167

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

4

16 回答 16

221

这是在 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 类,因为它更有效(内存和速度方面),如果您提前知道要解析什么。

于 2009-07-03T18:53:55.603 回答
85

为了完整起见,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。您可以通知 BeautifulSoup 在 HTTP 响应标头中找到的字符集以帮助解码,但这可能是错误的并且与<meta>在 HTML 本身中找到的标头信息冲突,这就是为什么上面使用 BeautifulSoup 内部类方法EncodingDetector.find_declared_encoding()来确保这种嵌入式编码提示赢得了配置错误的服务器。

使用时,如果响应具有mimetype,则requests该属性默认为 Latin-1 ,即使没有返回字符集也是如此。这与 HTTP RFC 一致,但在与 HTML 解析一起使用时会很痛苦,因此当Content-Type 标头中设置为 no 时,您应该忽略该属性。response.encodingtext/*charset

于 2014-03-22T20:52:44.210 回答
52

其他人推荐 BeautifulSoup,但使用lxml更好。尽管它的名字,它也用于解析和抓取 HTML。它比 BeautifulSoup 快得多,而且它甚至比 BeautifulSoup 更好地处理“损坏的”HTML(他们声名狼藉)。如果您不想学习 lxml API,它也有适用于 BeautifulSoup 的兼容性 API。

Ian Blicking 同意

没有理由再使用 BeautifulSoup,除非您使用的是 Google App Engine 或其他任何不纯 Python 的东西。

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
于 2009-08-03T15:34:01.313 回答
34
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'
于 2009-07-03T18:37:53.893 回答
11

以下代码是使用urllib2and检索网页中所有可用的链接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'))
于 2014-02-07T14:17:08.323 回答
9

链接可以在各种属性中,因此您可以传递这些属性的列表以供选择

例如,使用 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 的元素,其值以值作为前缀(在前)。

于 2019-04-10T22:14:21.397 回答
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]

在列表组合中,“如果'//'和'url.com'不在x中”是一种简单的方法来清理网站'内部'导航url等的url列表。

于 2013-10-07T10:46:27.257 回答
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。

于 2009-07-04T03:11:21.387 回答
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
于 2015-01-21T21:10:19.407 回答
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
于 2015-08-06T03:22:40.090 回答
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]))
于 2012-05-27T01:49:47.320 回答
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. 虽然它不会处理相对路径中的单点和双点,但到目前为止我还不需要它。如果您需要解析包含.././然后urlparse.urljoin的 URL 片段可能会派上用场。

注意:直接 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"
于 2016-06-10T22:38:00.413 回答
1

这是一个使用 @ars 接受的答案和BeautifulSoup4, requests, 和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)
于 2016-07-11T18:58:08.497 回答
1

经过以下更正(涵盖无法正常工作的情况)后,我找到了@Blairg23 working 的答案:

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。

于 2017-05-25T16:03:12.257 回答
1

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

# 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)
于 2019-10-10T20:08:20.127 回答
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']
于 2014-09-04T19:00:16.260 回答