lấy liên kết từ trang web bằng python và BeautifulSoup


Câu trả lời:


193

Đây là một đoạn ngắn sử dụng lớp SoupStrainer trong BeautifulSoup:

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

Tài liệu BeautifulSoup thực sự khá tốt và bao gồm một số tình huống điển hình:

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

Chỉnh sửa: Lưu ý rằng tôi đã sử dụng lớp SoupStrainer vì nó hiệu quả hơn một chút (bộ nhớ và tốc độ khôn ngoan), nếu bạn biết trước những gì bạn đang phân tích cú pháp.


13
+1, sử dụng bộ lọc súp là một ý tưởng tuyệt vời vì nó cho phép bạn tránh được nhiều phân tích cú pháp không cần thiết khi tất cả các bạn sau đó là các liên kết.
Evan Fosmark

4
Cảnh giác:/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
Trên phiên bản 3.2.1 của BeautifulSoup không có has_attr. Thay vào đó tôi thấy có một cái gì đó được gọi has_keyvà nó hoạt động.

2
Cập nhật cho python3
john doe

7
từ bs4 nhập BeautifulSoup. (không phải từ BeautifulSoup nhập BeautifulSoup ..) cần sửa.
Rishabh Agrahari

67

Để hoàn thiện, phiên bản BeautifulSoup 4, cũng sử dụng mã hóa được cung cấp bởi máy chủ:

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

hoặc phiên bản 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']

và một phiên bản sử dụng requeststhư viện , như được viết sẽ hoạt động trong cả Python 2 và 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'])

Cuộc soup.find_all('a', href=True)gọi tìm thấy tất cả các <a>yếu tố có hrefthuộc tính; các phần tử không có thuộc tính được bỏ qua.

BeautifulSoup 3 đã ngừng phát triển vào tháng 3 năm 2012; dự án mới thực sự nên sử dụng BeautifulSoup 4, luôn luôn.

Lưu ý rằng bạn nên để giải mã HTML từ byte sang BeautifulSoup . Bạn có thể thông báo cho BeautifulSoup về bộ ký tự được tìm thấy trong các tiêu đề phản hồi HTTP để hỗ trợ giải mã, nhưng điều này thể sai và mâu thuẫn với <meta>thông tin tiêu đề được tìm thấy trong chính HTML, đó là lý do tại sao ở trên sử dụng phương thức lớp bên trong BeautifulSoup EncodingDetector.find_declared_encoding()để đảm bảo rằng gợi ý mã hóa nhúng như vậy giành chiến thắng trên một máy chủ được cấu hình sai.

Với requests, response.encodingthuộc tính mặc định là Latin-1 nếu phản hồi có mô text/*phỏng, ngay cả khi không có ký tự nào được trả về. Điều này phù hợp với các RFC HTTP nhưng đau đớn khi được sử dụng với phân tích cú pháp HTML, vì vậy bạn nên bỏ qua thuộc tính đó khi không charsetđược đặt trong tiêu đề Kiểu nội dung.


Có cái gì đó giống như StrainedSoup cho bs4 không? (Tôi không cần nó ngay bây giờ nhưng chỉ cần tự hỏi, nếu có bạn có thể muốn thêm nó)
Antti Haapala

@AnttiHaapala: SoupStrainerý bạn là gì? Nó không đi đâu cả, nó vẫn là một phần của dự án .
Martijn Pieters

Có lý do nào mà mã này không chuyển "features =" cho hàm tạo BeautifulSoup không? BeautifulSoup cho tôi một cảnh báo về việc sử dụng trình phân tích cú pháp mặc định.
MikeB

1
@MikeB: khi tôi viết câu trả lời này, BeautifulSoup chưa đưa ra cảnh báo nếu bạn không.
Martijn Pieters

50

Những người khác đã khuyến nghị BeautifulSoup, nhưng sử dụng lxml sẽ tốt hơn nhiều . Mặc dù tên của nó, nó cũng là để phân tích cú pháp và cạo HTML. Nó nhanh hơn nhiều so với BeautifulSoup và thậm chí nó còn xử lý HTML "hỏng" tốt hơn BeautifulSoup (yêu cầu của họ để nổi tiếng). Nó cũng có API tương thích cho BeautifulSoup nếu bạn không muốn tìm hiểu API lxml.

Ian Blicking đồng ý .

Không còn lý do để sử dụng BeautifulSoup nữa, trừ khi bạn sử dụng Google App Engine hoặc một cái gì đó mà mọi thứ không hoàn toàn là Python không được phép.

lxml.html cũng hỗ trợ các bộ chọn CSS3 vì vậy loại điều này là tầm thường.

Một ví dụ với lxml và xpath sẽ như thế này:

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 sẽ sử dụng lxmllàm trình phân tích cú pháp mặc định nếu được cài đặt.
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'

Điều này đã giải quyết một vấn đề tôi gặp phải với mã của tôi. Cảm ơn bạn!
RJ

10

Đoạn mã sau là để lấy tất cả các liên kết có sẵn trong một trang web bằng cách sử dụng 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'))

8

Dưới mui xe BeautifulSoup hiện sử dụng lxml. Yêu cầu, lxml & danh sách hiểu biết làm cho một kết hợp giết người.

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]

Trong danh sách comp, "if '//' và 'url.com' không phải trong x" là một phương pháp đơn giản để xóa danh sách url của các url điều hướng 'nội bộ' của trang web, v.v.


1
Nếu đó là một bài đăng lại, tại sao bài đăng gốc không bao gồm: 1. request 2.list comp 3. logic để xóa các liên kết nội bộ & rác ?? Hãy thử và so sánh kết quả của hai bài đăng, danh sách comp của tôi thực hiện công việc tốt đáng ngạc nhiên khi cọ rửa các liên kết rác.
cheekybastard

OP không yêu cầu các tính năng đó và phần mà anh ta yêu cầu đã được đăng và giải quyết bằng phương pháp chính xác như bạn đăng. Tuy nhiên, tôi sẽ xóa downvote vì việc hiểu danh sách sẽ tăng thêm giá trị cho những người muốn có các tính năng đó và bạn có đề cập rõ ràng đến chúng trong phần thân bài. Ngoài ra, bạn có thể sử dụng đại diện :)
dotancohen

4

chỉ để nhận các liên kết, không có B.soup và 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]

Đối với các hoạt động phức tạp hơn, tất nhiên BSoup vẫn được ưu tiên.


7
Và nếu, ví dụ, có một cái gì đó giữa <ahref? Nói rel="nofollow"hay onclick="..."thậm chí chỉ là một dòng mới? stackoverflow.com/questions/1732348/ từ
dimo414

Có cách nào để lọc ra chỉ một số liên kết với điều này? như nói tôi chỉ muốn các liên kết có "Tập" trong liên kết?
nwgat

4

Kịch bản này thực hiện những gì bạn đang tìm kiếm, nhưng cũng giải quyết các liên kết tương đối đến các liên kết tuyệt đối.

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

Điều này không làm những gì ti có nghĩa là làm; nếu notify_links () không có root, thì nó không bao giờ trả về bất kỳ URL nào.
MikeB

4

Để tìm tất cả các liên kết, trong ví dụ này, chúng tôi sẽ sử dụng mô-đun urllib2 cùng với re.module * Một trong những hàm mạnh nhất trong mô-đun re là "re.findall ()". Trong khi re.search () được sử dụng để tìm kết quả khớp đầu tiên cho mẫu, re.findall () tìm tất cả các kết quả khớp và trả về chúng dưới dạng danh sách các chuỗi, với mỗi chuỗi đại diện cho một kết quả khớp *

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

Tại sao không sử dụng biểu thức thông thường:

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
Tôi rất muốn có thể hiểu điều này, tôi có thể tìm hiểu (r"<a.*?\s*href=\"(.*?)\".*?>(.*?)</a>", page)phương tiện ở đâu một cách hiệu quả ? cảm ơn!
dùng1063287

9
Thực sự là một ý tưởng tồi. HTML bị hỏng ở khắp mọi nơi.
Ufoguy

2
Tại sao không sử dụng các biểu thức thông thường để phân tích cú pháp html: stackoverflow.com/questions/1732348/
Kẻ

@ user1063287, web có đầy đủ các hướng dẫn regex. Rất đáng để bạn dành thời gian đọc một cặp vợ chồng. Trong khi các RE có thể thực sự trở nên hỗn độn, thì cái bạn đang hỏi là khá cơ bản.
alexis

3

Liên kết có thể nằm trong nhiều thuộc tính khác nhau để bạn có thể chuyển danh sách các thuộc tính đó để chọn

ví dụ: với thuộc tính src và href (ở đây tôi đang sử dụng toán tử start with ^ để chỉ định rằng một trong hai giá trị thuộc tính này bắt đầu bằng http. Bạn có thể điều chỉnh điều này theo yêu cầu

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)

Thuộc tính = bộ chọn giá trị

[attr ^ = giá trị]

Đại diện cho các phần tử có tên thuộc tính của attr có giá trị được tiền tố (đứng trước) theo giá trị.


1

Dưới đây là một ví dụ sử dụng @ars câu trả lời được chấp nhận và các BeautifulSoup4, requestswgetmô-đun để xử lý các tải.

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

Tôi đã tìm thấy câu trả lời bởi @ Blairg23 hoạt động, sau khi sửa lỗi sau (bao gồm kịch bản không hoạt động chính xác):

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)

Đối với Python 3:

urllib.parse.urljoin phải được sử dụng để có được URL đầy đủ thay thế.


1

Trình phân tích cú pháp riêng của BeatiouslySoup có thể bị chậm. Có thể khả thi hơn khi sử dụng lxml có khả năng phân tích cú pháp trực tiếp từ một URL (với một số hạn chế được đề cập dưới đây).

import lxml.html

doc = lxml.html.parse(url)

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

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

Mã ở trên sẽ trả về các liên kết như hiện tại và trong hầu hết các trường hợp chúng sẽ là các liên kết tương đối hoặc tuyệt đối từ gốc trang. Vì trường hợp sử dụng của tôi là chỉ trích xuất một loại liên kết nhất định, bên dưới là phiên bản chuyển đổi các liên kết thành URL đầy đủ và tùy chọn chấp nhận mô hình toàn cầu như thế nào *.mp3. Mặc dù vậy, nó sẽ không xử lý các dấu chấm đơn và kép trong các đường dẫn tương đối, nhưng cho đến nay tôi không có nhu cầu về nó. Nếu bạn cần phân tích các đoạn URL có chứa ../hoặc ./sau đó urlparse.urljoin có thể có ích.

LƯU Ý : Phân tích cú pháp url lxml trực tiếp không xử lý tải từ httpsvà không chuyển hướng, vì vậy, vì lý do này, phiên bản bên dưới đang sử dụng 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

Cách sử dụng như sau:

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"

lxmlChỉ có thể xử lý đầu vào hợp lệ, làm thế nào nó có thể thay thế BeautifulSoup?
alexis

@alexis: Tôi nghĩ lxml.htmllà khoan dung hơn một chút so với lxml.etree. Nếu đầu vào của bạn không được định dạng tốt thì bạn có thể đặt rõ ràng trình phân tích cú pháp BeautifulSoup: lxml.de/elementsoup.html . Và nếu bạn đồng hành cùng BeatiouslySoup thì BS3 là lựa chọn tốt hơn.
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

Có thể có nhiều liên kết trùng lặp cùng với cả liên kết bên ngoài và bên trong. Để phân biệt giữa hai và chỉ nhận các liên kết duy nhất bằng cách sử dụng các bộ:

# 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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.