使用 Python 和 BeautifulSoup 从网页中提取链接

183

如何使用Python检索网页的链接并复制链接的URL地址?


这是一个更新的代码片段,用30行代码完美地实现了你所要求的功能。 https://github.com/mujeebishaque/extract-urls - Mujeeb Ishaque
我尝试了这个链接,并得到了像这样的输出/info-service/downloads/#unserekataloge'。无法获取完整的可访问链接吗?而不仅仅是子链接的一部分?我想获取网站上所有可用pdf的链接。@MujeebIshaque - x89
16个回答

240

这里是一个使用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
4
注意:/usr/local/lib/python2.7/site-packages/bs4/__init__.py:128: UserWarning: BeautifulSoup构造函数中的"parseOnlyThese"参数已更名为"parse_only。" - BenDundee
37
在BeautifulSoup的3.2.1版本中,没有has_attr。相反,我看到有一个叫做has_key的东西,并且它可以使用。 - user2796118
7
从bs4模块导入BeautifulSoup。(不是从BeautifulSoup模块导入BeautifulSoup。)需要更正。 - Rishabh Agrahari
7
Python3和最新的bs4更新的代码 - https://gist.github.com/PandaWhoCodes/7762fac08c4ed005cec82204d7abd61b - Ashish Cherian
显示剩余6条评论

98

为了完整起见,以下是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 库的版本,如下所示即可在 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) 调用会找到所有拥有 href 属性的 <a> 元素,没有此属性的元素会被跳过。

BeautifulSoup 3 在2012年3月停止开发;新项目应该始终使用 BeautifulSoup 4。

请注意,您应该将 HTML 从字节中解码交给 BeautifulSoup。您可以通知 BeautifulSoup 找到在 HTTP 响应标头中找到的字符集以帮助解码,但这个方法可能会错误并与在 HTML 中找到的 <meta> 标头信息冲突,因此上面的示例使用 BeautifulSoup 内部类方法 EncodingDetector.find_declared_encoding() 确保这样的嵌入式编码提示胜过配置错误的服务器。

对于 requests,如果响应具有 text/* MIME 类型,则 response.encoding 属性默认为 Latin-1,即使未返回字符集也是如此。这与 HTTP RFCs 一致,但在 HTML 解析中使用时很麻烦,所以当 Content-Type 标头中没有设置 charset 时,应忽略该属性。


有没有类似于bs4的StrainedSoup?(我现在不需要,只是想知道,如果有的话,你可能会想要添加它) - Antti Haapala -- Слава Україні
@AnttiHaapala:你是指 SoupStrainer 吗?它并没有消失,仍然是该项目的一部分 - Martijn Pieters
这段代码为什么没有将“features=”传递给BeautifulSoup构造函数呢?BeautifulSoup会警告您使用默认解析器。 - MikeB
2
@MikeB:当我写这个答案时,BeautifulSoup还没有发出警告,如果你没有。 - Martijn Pieters

54

虽然有人推荐BeautifulSoup,但最好使用lxml。尽管名字中有"XML",但它也可以用于解析和抓取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

27
如果已经安装了 lxml,BeautifulSoup 4将使用它作为默认解析器。 - Martijn Pieters

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

3
这解决了我在代码中遇到的问题。谢谢! - R J

12
以下代码是使用 urllib2BeautifulSoup4 来检索网页中所有可用链接的示例代码:
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'))

11

链接可以包含多种属性,因此您可以将这些属性的列表传递给select

例如,使用srchref属性(这里我使用以^运算符开头来指定这些属性值都是以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 且值以 value 开头的元素。

通常使用的操作符还包括 $(以某些值结尾)和 *(包含某些值)。有关完整语法列表,请参见上面的链接。


9
在幕后,BeautifulSoup 现在使用 lxml。Requests、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 '//' and 'url.com' not in x”是一种简单的方法,用于清理网址列表中的“内部”导航网址等。

1
如果这是一个转帖,为什么原始帖子没有包括:1.请求 2.列表推导式 3.清理站点内部和垃圾链接的逻辑?尝试比较两个帖子的结果,我的列表推导式在清理垃圾链接方面表现出色。 - cheekybastard
OP并没有要求那些功能,而他要求的部分已经被发布并使用了与您发布的完全相同的方法解决。然而,我会取消踩一下,因为列表推导确实为那些需要这些功能的人增加了价值,并且您在帖子正文中明确提到了它们。此外,您可以使用rep :) - dotancohen

5

这个脚本可以满足您的需求,而且还能将相对链接转换为绝对链接。

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

这不是它应该做的事情;如果 resolve_links() 没有根目录,它就不会返回任何 URL。 - MikeB

5

为了找到所有的链接,在这个例子中我们将使用urllib2模块和re模块。 *在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

4

仅获取链接,无需使用 B.soup 和正则表达式:

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="..."甚至只是一个换行符?https://dev59.com/X3I-5IYBdhLWcg3wq6do#1732454 - dimo414
有没有办法用这个过滤掉只有一些链接?比如说,我只想要链接中带有“Episode”的链接? - nwgat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接