PythonとBeautifulSoupを使用してWebページからリンクを取得する


回答:


193

以下は、BeautifulSoupのSoupStrainerクラスを使用した短いスニペットです。

import httplib2
from bs4 import BeautifulSoup, SoupStrainer

http = httplib2.Http()
status, response = http.request('http://www.nytimes.com')

for link in BeautifulSoup(response, parse_only=SoupStrainer('a')):
    if link.has_attr('href'):
        print(link['href'])

BeautifulSoupのドキュメントは実際には非常に優れており、多くの典型的なシナリオをカバーしています。

https://www.crummy.com/software/BeautifulSoup/bs4/doc/

編集:事前に何を解析しているかがわかっている場合は、SoupStrainerクラスを使用したことに注意してください(メモリと速度の点で)少し​​効率的です。


13
+1、スープストレーナーを使用することは素晴らしいアイデアです。これは、必要なものがすべてリンクであるときに、多くの不要な解析を回避できるためです。
Evan Fosmark 2009

4
ヘッズアップ:/usr/local/lib/python2.7/site-packages/bs4/__init__.py:128: UserWarning: The "parseOnlyThese" argument to the BeautifulSoup constructor has been renamed to "parse_only."
BenDundee 2013

27
BeautifulSoupのバージョン3.2.1にはありませんhas_attr。代わりに、何かが呼び出さhas_keyれて機能することがわかります。

2
python3の更新
john doe

7
bs4からBeautifulSoupをインポートします。(BeautifulSoupからではなく、BeautifulSoupをインポートしてください。)修正が必要です。
Rishabh Agrahari

67

完全を期すために、BeautifulSoup 4バージョンでは、サーバーから提供されたエンコーディングも利用しています。

from bs4 import BeautifulSoup
import urllib.request

parser = 'html.parser'  # or 'lxml' (preferred) or 'html5lib', if installed
resp = urllib.request.urlopen("http://www.gpsbasecamp.com/national-parks")
soup = BeautifulSoup(resp, parser, from_encoding=resp.info().get_param('charset'))

for link in soup.find_all('a', href=True):
    print(link['href'])

またはPython 2バージョン:

from bs4 import BeautifulSoup
import urllib2

parser = 'html.parser'  # or 'lxml' (preferred) or 'html5lib', if installed
resp = urllib2.urlopen("http://www.gpsbasecamp.com/national-parks")
soup = BeautifulSoup(resp, parser, from_encoding=resp.info().getparam('charset'))

for link in soup.find_all('a', href=True):
    print link['href']

そして、requestsライブラリ2を使用するバージョン。これは、記述されているように、Python 2と3の両方で機能します。

from bs4 import BeautifulSoup
from bs4.dammit import EncodingDetector
import requests

parser = 'html.parser'  # or 'lxml' (preferred) or 'html5lib', if installed
resp = requests.get("http://www.gpsbasecamp.com/national-parks")
http_encoding = resp.encoding if 'charset' in resp.headers.get('content-type', '').lower() else None
html_encoding = EncodingDetector.find_declared_encoding(resp.content, is_html=True)
encoding = html_encoding or http_encoding
soup = BeautifulSoup(resp.content, parser, from_encoding=encoding)

for link in soup.find_all('a', href=True):
    print(link['href'])

soup.find_all('a', href=True)コールはすべて見つけ<a>持つ要素hrefの属性を、属性のない要素はスキップされます。

BeautifulSoup 3は2012年3月に開発を停止しました。新しいプロジェクトでは、常にBeautifulSoup 4を使用する必要があります。

HTMLをバイトからBeautifulSoupにデコードしたままにする必要があることに注意してください。デコードを支援するために、HTTP応答ヘッダーにある文字セットをBeautifulSoupに通知できますが、これ誤りであり<meta>、HTML自体にあるヘッダー情報と競合する可能性があります。そのため、上記ではBeautifulSoup内部クラスメソッドEncodingDetector.find_declared_encoding()を使用して、このような埋め込まれたエンコードのヒントは、誤って構成されたサーバーに勝ちます。

ではrequests、文字セットが返されなかった場合でもresponse.encoding、応答にtext/*MIME タイプがある場合、属性はデフォルトでLatin-1 になります。これはHTTP RFCと一貫していますが、HTML解析で使用すると苦痛です。そのためcharset、Content-Typeヘッダーに何も設定されていない場合は、その属性を無視してください。


bs4用のStrainedSoupのようなものはありますか?(私は今それを必要としませんが、もしあれば、それを追加したいと思うかもしれません)
Antti Haapala 2017


このコードがBeautifulSoupコンストラクターに「features =」を渡さない理由はありますか?BeautifulSoupは、デフォルトのパーサーの使用に関する警告を表示します。
MikeB

1
@MikeB:私がこの回答を書いたとき、BeautifulSoupはまだ警告していませんでした。
Martijn Pieters

50

他の人はBeautifulSoupを推奨していますが、lxmlを使用する方がはるかに優れています。その名前にもかかわらず、これはHTMLの解析とスクレイピングにも使用されます。それはBeautifulSoupよりはるかに高速で、BeautifulSoupよりも「壊れた」HTMLをより適切に処理します(名声を主張します)。lxml APIを学習したくない場合は、BeautifulSoupの互換性APIもあります。

Ian Blickingも同意する

Google App Engineを使用しているか、純粋にPython以外のものが許可されていない場合を除いて、BeautifulSoupを使用する理由はありません。

lxml.htmlはCSS3セレクターもサポートしているため、このようなことは簡単です。

lxmlとxpathの例は次のようになります。

import urllib
import lxml.html
connection = urllib.urlopen('http://www.nytimes.com')

dom =  lxml.html.fromstring(connection.read())

for link in dom.xpath('//a/@href'): # select the url in href for all a tags(links)
    print link

23
BeautifulSoup 4がlxmlインストールされている場合、デフォルトのパーサーとして使用されます。
Martijn Pieters

28
import urllib2
import BeautifulSoup

request = urllib2.Request("http://www.gpsbasecamp.com/national-parks")
response = urllib2.urlopen(request)
soup = BeautifulSoup.BeautifulSoup(response)
for a in soup.findAll('a'):
  if 'national-park' in a['href']:
    print 'found a url with national-park in the link'

これにより、コードで発生した問題が解決しました。ありがとうございました!
RJは

10

次のコードは、urllib2andを使用してWebページで使用可能なすべてのリンクを取得するためのものですBeautifulSoup4

import urllib2
from bs4 import BeautifulSoup

url = urllib2.urlopen("http://www.espncricinfo.com/").read()
soup = BeautifulSoup(url)

for line in soup.find_all('a'):
    print(line.get('href'))

8

内部では、BeautifulSoupはlxmlを使用します。リクエスト、lxml、リストの理解がキラーコンボになります。

import requests
import lxml.html

dom = lxml.html.fromstring(requests.get('http://www.nytimes.com').content)

[x for x in dom.xpath('//a/@href') if '//' in x and 'nytimes.com' not in x]

リストコンプでは、「if '//'および 'url.com' not in x」は、サイトの「内部」ナビゲーションURLなどのURLリストをスクラブする簡単な方法です。


1
それが再投稿である場合、元の投稿になぜ含まれていないのですか:1.リクエスト2.リストコンポーネント3.サイトの内部リンクとジャンクリンクをスクラブするロジック?2つの投稿の結果を比較してみてください。私のリストコンプは、ジャンクリンクのスクラブを驚くほどうまく行っています。
cheekybastard 2013

OPはこれらの機能を要求せず、OPが要求した部分は既に投稿されており、投稿したのとまったく同じ方法で解決されました。ただし、リストの理解はそれらの機能を必要とする人々に価値をもたらし、投稿の本文でそれらを明示的に言及するため、反対票は削除します。また、repを使用することもできます:)
dotancohen

4

リンクを取得するためだけに、B.soupとregexなしで:

import urllib2
url="http://www.somewhere.com"
page=urllib2.urlopen(url)
data=page.read().split("</a>")
tag="<a href=\""
endtag="\">"
for item in data:
    if "<a href" in item:
        try:
            ind = item.index(tag)
            item=item[ind+len(tag):]
            end=item.index(endtag)
        except: pass
        else:
            print item[:end]

より複雑な操作の場合は、もちろんBSoupが依然として推奨されます。


7
そして、たとえば、間に何かがある<ahref?言いますrel="nofollow"か、onclick="..."それとも単に新しい行ですか? stackoverflow.com/questions/1732348/...
dimo414

これで一部のリンクのみを除外する方法はありますか?リンクに「エピソード」が含まれているリンクだけが欲しいと言うような?
nwgat

4

このスクリプトは、探していることを行いますが、相対リンクを絶対リンクに解決します。

import urllib
import lxml.html
import urlparse

def get_dom(url):
    connection = urllib.urlopen(url)
    return lxml.html.fromstring(connection.read())

def get_links(url):
    return resolve_links((link for link in get_dom(url).xpath('//a/@href')))

def guess_root(links):
    for link in links:
        if link.startswith('http'):
            parsed_link = urlparse.urlparse(link)
            scheme = parsed_link.scheme + '://'
            netloc = parsed_link.netloc
            return scheme + netloc

def resolve_links(links):
    root = guess_root(links)
    for link in links:
        if not link.startswith('http'):
            link = urlparse.urljoin(root, link)
        yield link  

for link in get_links('http://www.google.com'):
    print link

これはtiが意図することを行いません。resolve_links()にルートがない場合、URLが返されることはありません。
MikeB

4

すべてのリンクを見つけるために、この例ではurllib2モジュールをre.moduleと一緒に使用します * reモジュールの最も強力な関数の1つは「re.findall()」です。re.search()はパターンの最初の一致を見つけるために使用されますが、re.findall()はすべて の一致を検索、それらを文字列のリストとして返します。各文字列は1つの一致を表します*

import urllib2

import re
#connect to a URL
website = urllib2.urlopen(url)

#read html code
html = website.read()

#use re.findall to get all the links
links = re.findall('"((http|ftp)s?://.*?)"', html)

print links

3

なぜ正規表現を使わないのですか?

import urllib2
import re
url = "http://www.somewhere.com"
page = urllib2.urlopen(url)
page = page.read()
links = re.findall(r"<a.*?\s*href=\"(.*?)\".*?>(.*?)</a>", page)
for link in links:
    print('href: %s, HTML text: %s' % (link[0], link[1]))

1
これを理解できるようになりたいのですが、どこでどういう(r"<a.*?\s*href=\"(.*?)\".*?>(.*?)</a>", page)意味なのかを効率的に知ることができますか?ありがとう!
user1063287 2013

9
本当に悪い考えです。どこでも壊れたHTML。
Ufoguy 2014年

2
なぜパースHTMLに正規表現を使用しない:stackoverflow.com/questions/1732348/...
allcaps

@ user1063287、ウェブは正規表現のチュートリアルでいっぱいです。カップルを読むのは、あなたの時間に十分価値があります。REは非常に複雑になる可能性がありますが、あなたが尋ねているのはかなり基本的なものです。
アレクシス、2016年

3

リンクはさまざまな属性内に含めることができるため、選択する属性のリストを渡すことができます

たとえば、srcおよびhref属性を使用します(ここでは、starts with ^演算子を使用して、これらの属性値のいずれかがhttpで始まることを指定しています。必要に応じてこれを調整できます

from bs4 import BeautifulSoup as bs
import requests
r = requests.get('https://stackoverflow.com/')
soup = bs(r.content, 'lxml')
links = [item['href'] if item.get('href') is not None else item['src'] for item in soup.select('[href^="http"], [src^="http"]') ]
print(links)

属性=値セレクター

[attr ^ = value]

値の前に(前に)値が付いた属性名がattrの要素を表します。


1

ここで@ars受け入れ答えと使用例だBeautifulSoup4requestsと、wgetダウンロードを処理するためのモジュールを。

import requests
import wget
import os

from bs4 import BeautifulSoup, SoupStrainer

url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/eeg-mld/eeg_full/'
file_type = '.tar.gz'

response = requests.get(url)

for link in BeautifulSoup(response.content, 'html.parser', parse_only=SoupStrainer('a')):
    if link.has_attr('href'):
        if file_type in link['href']:
            full_path = url + link['href']
            wget.download(full_path)

1

@ Blairg23が次の修正(正しく機能しなかったシナリオをカバー)の後に、私は@ Blairg23が機能しているという答えを見つけました。

for link in BeautifulSoup(response.content, 'html.parser', parse_only=SoupStrainer('a')):
    if link.has_attr('href'):
        if file_type in link['href']:
            full_path =urlparse.urljoin(url , link['href']) #module urlparse need to be imported
            wget.download(full_path)

Python 3の場合:

urllib.parse.urljoin 代わりに完全なURLを取得するために使用する必要があります。


1

BeatifulSoup自体のパーサーは遅くなる可能性があります。URLから直接解析できるlxmlを使用する方が現実的かもしれません(以下にいくつかの制限があります)。

import lxml.html

doc = lxml.html.parse(url)

links = doc.xpath('//a[@href]')

for link in links:
    print link.attrib['href']

上記のコードはリンクをそのまま返します。ほとんどの場合、それらはサイトのルートからの相対リンクまたは絶対リンクです。私の使用例は特定のタイプのリンクのみを抽出することでしたので、以下は、リンクを完全なURLに変換し、オプションでのようなグロブパターンを受け入れるバージョンです*.mp3。ただし、相対パスのシングルドットとダブルドットは処理されませんが、これまでのところ必要はありませんでした。../またはを含むURLフラグメントを解析する必要がある場合は./urlparse.urljoinが便利です。

:直接lxml url解析はからのロードを処理httpsせず、リダイレクトも実行しないため、このため、以下のバージョンではurllib2+ を使用していlxmlます。

#!/usr/bin/env python
import sys
import urllib2
import urlparse
import lxml.html
import fnmatch

try:
    import urltools as urltools
except ImportError:
    sys.stderr.write('To normalize URLs run: `pip install urltools --user`')
    urltools = None


def get_host(url):
    p = urlparse.urlparse(url)
    return "{}://{}".format(p.scheme, p.netloc)


if __name__ == '__main__':
    url = sys.argv[1]
    host = get_host(url)
    glob_patt = len(sys.argv) > 2 and sys.argv[2] or '*'

    doc = lxml.html.parse(urllib2.urlopen(url))
    links = doc.xpath('//a[@href]')

    for link in links:
        href = link.attrib['href']

        if fnmatch.fnmatch(href, glob_patt):

            if not href.startswith(('http://', 'https://' 'ftp://')):

                if href.startswith('/'):
                    href = host + href
                else:
                    parent_url = url.rsplit('/', 1)[0]
                    href = urlparse.urljoin(parent_url, href)

                    if urltools:
                        href = urltools.normalize(href)

            print href

使用方法は次のとおりです。

getlinks.py http://stackoverflow.com/a/37758066/191246
getlinks.py http://stackoverflow.com/a/37758066/191246 "*users*"
getlinks.py http://fakedomain.mu/somepage.html "*.mp3"

lxml有効な入力のみを処理できますが、どのように置き換えることができBeautifulSoupますか?
アレクシス、2016年

@alexis:とlxml.html比べて少し寛大だと思いますlxml.etree。入力が整形式でない場合は、BeautifulSoupパーサーを明示的に設定できます:lxml.de/elementsoup.html。BeatifulSoupを使用する場合は、BS3の方が適しています。
ccpizza

0
import urllib2
from bs4 import BeautifulSoup
a=urllib2.urlopen('http://dir.yahoo.com')
code=a.read()
soup=BeautifulSoup(code)
links=soup.findAll("a")
#To get href part alone
print links[0].attrs['href']

0

外部リンクと内部リンクの両方と一緒に多くの重複リンクが存在する可能性があります。2つを区別し、セットを使用して一意のリンクを取得するには:

# Python 3.
import urllib    
from bs4 import BeautifulSoup

url = "http://www.espncricinfo.com/"
resp = urllib.request.urlopen(url)
# Get server encoding per recommendation of Martijn Pieters.
soup = BeautifulSoup(resp, from_encoding=resp.info().get_param('charset'))  
external_links = set()
internal_links = set()
for line in soup.find_all('a'):
    link = line.get('href')
    if not link:
        continue
    if link.startswith('http'):
        external_links.add(link)
    else:
        internal_links.add(link)

# Depending on usage, full internal links may be preferred.
full_internal_links = {
    urllib.parse.urljoin(url, internal_link) 
    for internal_link in internal_links
}

# Print all unique external and full internal links.
for link in external_links.union(full_internal_links):
    print(link)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.