使用python和BeautifulSoup从网页检索链接


Answers:


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

27
在BeautifulSoup的3.2.1版本上没有has_attr。取而代之的是,我看到有一种叫它的东西has_key,它可以工作。

2
python3更新
john doe

7
从bs4导入BeautifulSoup。(不是从BeautifulSoup导入BeautifulSoup ..)需要更正。
利沙伯·阿格拉哈里(Rishabh Agrahari)'17年

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']

以及使用requestslibrary的版本,该版本在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/*未返回任何字符集,如果响应具有mimetype ,该属性也会默认为Latin-1 。这与HTTP RFC一致,但是在与HTML解析一起使用时会很麻烦,因此当charsetContent-Type标头中设置为no时,应忽略该属性。


bs4是否有像StrainedSoup这样的东西?(我现在不需要它,只是想知道是否有可能要添加它)
Antti Haapala

@AnttiHaapala:SoupStrainer你的意思是?它没有去任何地方,它仍然是项目的一部分
马丁·皮特斯

此代码没有将“ features =“传递给BeautifulSoup构造函数的原因吗?BeautifulSoup给我有关使用默认解析器的警告。
MikeB

1
@MikeB:当我写这个答案时,BeautifulSoup尚未发出警告(如果您没有发出警告)。
马丁·彼得斯

50

其他人推荐了BeautifulSoup,但是使用lxml更好。尽管它的名字,它也用于解析和抓取HTML。它比BeautifulSoup快得多,而且甚至比BeautifulSoup(他们声名fa起)更好地处理“断”的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
lxml如果已安装,BeautifulSoup 4将用作默认解析器。
马丁·彼得斯

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

以下代码使用urllib2和检索网页中所有可用的链接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和列表理解能力是一个杀手comb。

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]

在list comp中,“ if'//'和'url.com'不是x”是一种简单的方法,可以清理网站“内部”导航网址等的网址列表。


1
如果是重新发布,为什么原始发布不包括:1.请求2.list comp 3.清理网站内部和垃圾链接的逻辑?尝试比较两个帖子的结果,我的列表组件在清理垃圾链接方面做得非常好。
cheekybastard

OP并没有要求这些功能,而他要求的零件已经使用与您发布相同的方法发布并解决了。但是,由于清单理解确实为确实想要这些功能的人们增加了价值,因此我将删除那些不赞成投票的人,并且您确实在帖子正文中明确提及了它们。另外,您可以使用rep :)
dotancohen 2013年

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
举例来说,如果介于<a和之间href?说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模块中最强大的功能之一是“ re.findall()”。使用re.search()查找模式的第一个匹配项时,re.findall()查找所有 匹配项并将它们作为字符串列表返回,每个字符串代表一个匹配项*

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确实很令人费解,但您要问的是相当基本的。
亚历克西斯

3

链接可以位于各种属性中,因此您可以传递这些属性的列表以进行选择

例如,使用src和href属性(在这里,我使用^开头的运算符来指定这些属性值之一以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接受的答案和一个例子BeautifulSoup4requestswget模块来处理下载。

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工作的答案:

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的版本,该版本可以选择接受glob模式(如)*.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
亚历克西斯

@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

外部链接和内部链接都可以有很多重复的链接。要区分两者并仅使用集合获得唯一链接:

# 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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.