ดึงลิงค์จากหน้าเว็บด้วย python และ BeautifulSoup


คำตอบ:


193

นี่เป็นตัวอย่างสั้น ๆ ที่ใช้คลาส SoupStrainer ใน 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'])

จริง ๆ แล้วเอกสาร 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: The "parseOnlyThese" argument to the BeautifulSoup constructor has been renamed to "parse_only."
BenDundee

27
กับรุ่น 3.2.1 BeautifulSoup 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ไลบรารีซึ่งตามที่เขียนจะใช้ได้ทั้ง 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 โครงการใหม่ควรใช้ BeautifulSoup 4 เสมอ

โปรดทราบว่าคุณควรจะออกจากการถอดรหัส HTML จากไบต์เพื่อ BeautifulSoup คุณสามารถแจ้ง BeautifulSoup ของชุดอักขระที่พบในส่วนหัวการตอบกลับ HTTP เพื่อช่วยในการถอดรหัส แต่อาจผิดและขัดแย้งกับ<meta>ข้อมูลส่วนหัวที่พบใน HTML เองซึ่งเป็นสาเหตุที่ใช้วิธีการเรียนภายใน BeautifulSoup EncodingDetector.find_declared_encoding()เพื่อให้แน่ใจว่า คำแนะนำการเข้ารหัสแบบฝังดังกล่าวจะชนะเหนือเซิร์ฟเวอร์ที่กำหนดค่าผิดพลาด

ด้วยความrequestsที่response.encodingเป็นค่าเริ่มต้นแอตทริบิวต์ละติน-1 ถ้าการตอบสนองที่มีtext/*ชนิด mime แม้ว่าจะไม่มี characterset ถูกส่งกลับ สิ่งนี้สอดคล้องกับ HTTP RFCs แต่เจ็บปวดเมื่อใช้กับการแยกวิเคราะห์ HTML ดังนั้นคุณควรละเว้นแอตทริบิวต์นั้นเมื่อไม่มีการcharsetตั้งค่าในส่วนหัวของประเภทเนื้อหา


มีบางอย่างเช่น StrainedSoup สำหรับ bs4 หรือไม่ (ฉันไม่ต้องการมัน แต่ตอนนี้เพียงแค่สงสัยว่าถ้ามีคุณต้องการอาจต้องการที่จะเพิ่มที่)
Antti Haapala

@AnttiHaapala: SoupStrainerคุณหมายถึงอะไร มันไม่ได้ไปทุกที่ก็ยังคงเป็นส่วนหนึ่งของโครงการ
Martijn Pieters

มีเหตุผลที่รหัสนี้ไม่ส่ง "features =" ไปยังตัวสร้าง BeautifulSoup หรือไม่? BeautifulSoup แจ้งเตือนฉันเกี่ยวกับการใช้ตัวแยกวิเคราะห์เริ่มต้น
MikeB

1
@ MikeB: เมื่อฉันเขียนคำตอบนี้ BeautifulSoup ยังไม่ได้เตือนถ้าคุณไม่ได้
Martijn Pieters

50

อื่น ๆ ได้แนะนำ BeautifulSoup แต่มันเป็นเรื่องที่ดีมากกับการใช้lxml แม้จะมีชื่อมันก็ยังสำหรับการแยกและขูด HTML มันเร็วกว่า BeautifulSoup มากและมันยังจัดการ HTML ที่ "เสียหาย" ได้ดีกว่า BeautifulSoup (พวกเขาอ้างสิทธิ์ในชื่อเสียง) มันมี API ที่เข้ากันได้สำหรับ BeautifulSoup ด้วยหากคุณไม่ต้องการเรียนรู้ lxml API

เอียน Blicking ตกลง

ไม่มีเหตุผลที่จะใช้ BeautifulSoup อีกต่อไปเว้นแต่คุณจะใช้ Google App Engine หรือบางสิ่งที่ไม่อนุญาตให้ใช้ Python อย่างแท้จริง

lxml.html ยังรองรับ CSS3 selectors ดังนั้นสิ่งเล็ก ๆ น้อย ๆ เช่นนี้

ตัวอย่างที่มี 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

รหัสต่อไปนี้คือการดึงลิงค์ทั้งหมดที่มีอยู่ในเว็บเพจโดยใช้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 & รายการความเข้าใจทำให้เป็นคำสั่งผสม

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' ไม่ได้อยู่ใน x" เป็นวิธีการง่าย ๆ ในการขัดรายการ URL ของ URL การนาวิเกต 'ภายใน' ของไซต์เป็นต้น


1
ถ้าเป็น repost เหตุใดจึงไม่มีโพสต์ดั้งเดิมรวมอยู่: 1. ร้องขอ 2.list comp 3. ตรรกะในการขัดลิงก์ภายใน & ลิงก์ขยะ? ลองและเปรียบเทียบผลลัพธ์ของการโพสต์สองรายการ comp ของฉันทำงานได้ดีอย่างน่าประหลาดใจในการขัดลิงก์ขยะ
cheekybastard

OP ไม่ขอคุณสมบัติเหล่านั้นและส่วนที่เขาขอได้ถูกโพสต์ไปแล้วและแก้ไขโดยใช้วิธีการเดียวกันกับที่คุณโพสต์ อย่างไรก็ตามฉันจะลบ downvote เนื่องจากความเข้าใจในรายการจะเพิ่มคุณค่าให้กับผู้ที่ต้องการคุณสมบัติเหล่านั้นและคุณจะกล่าวถึงพวกเขาอย่างชัดเจนในเนื้อหาของโพสต์ นอกจากนี้คุณยังสามารถใช้ตัวแทน :)
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
ตัวอย่างเช่นถ้ามีบางอย่างอยู่ระหว่าง<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

นี่ไม่ได้ทำในสิ่งที่ตั้งใจจะทำ ถ้า resol_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

9
เป็นความคิดที่เลวจริงๆ HTML ที่เสียหายทุกที่
Ufoguy

2
ทำไมไม่ใช้นิพจน์ทั่วไปในการแยกวิเคราะห์ html: stackoverflow.com/questions/1732348/…
allcaps

@ user1063287 เว็บเต็มไปด้วยบทเรียนของ regex มันคุ้มค่ากับเวลาของคุณที่จะอ่านคู่ ในขณะที่ REs สามารถทำให้สับสนได้จริงๆสิ่งที่คุณถามเกี่ยวกับนั้นค่อนข้างธรรมดา
อเล็กซิส

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 ^ = ค่า]

แสดงให้เห็นถึงองค์ประกอบที่มีชื่อคุณลักษณะของ attr ที่มีค่าจะนำหน้า (นำหน้า) โดยค่า


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)

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 สามารถช้า อาจเป็นไปได้มากกว่าที่จะใช้lxmlซึ่งสามารถแยกวิเคราะห์โดยตรงจาก URL (โดยมีข้อ จำกัด บางอย่างที่กล่าวถึงด้านล่าง)

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และไม่ได้ทำการเปลี่ยนเส้นทางดังนั้นด้วยเหตุนี้รุ่นดังต่อไปนี้คือการใช้+urllib2lxml

#!/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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.