ambil tautan dari halaman web menggunakan python dan BeautifulSoup

Jawaban:

193

Berikut cuplikan singkat menggunakan kelas SoupStrainer di 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'])

Dokumentasi BeautifulSoup sebenarnya cukup bagus, dan mencakup sejumlah skenario khas:

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

Sunting: Perhatikan bahwa saya menggunakan kelas SoupStrainer karena ini sedikit lebih efisien (memori dan kecepatan bijaksana), jika Anda tahu apa yang Anda parsing sebelumnya.

ars
sumber
13
+1, menggunakan saringan sup adalah ide bagus karena memungkinkan Anda menghindari banyak penguraian yang tidak perlu saat semua yang Anda cari adalah tautannya.
Evan Fosmark
4
Kepala:/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
Pada versi 3.2.1 dari BeautifulSoup tidak ada has_attr. Sebaliknya saya melihat ada sesuatu yang disebut has_keydan berfungsi.
2
Pembaruan untuk python3
john doe
7
dari bs4 import BeautifulSoup. (bukan dari impor BeautifulSoup BeautifulSoup ..) koreksi diperlukan.
Rishabh Agrahari
67

Demi kelengkapannya, versi BeautifulSoup 4, menggunakan pengkodean yang disediakan oleh server juga:

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

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

dan versi menggunakan requestspustaka , yang seperti yang ditulis akan bekerja di Python 2 dan 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'])

The soup.find_all('a', href=True)panggilan menemukan semua <a>elemen yang memiliki hrefatribut; elemen tanpa atribut dilewati.

BeautifulSoup 3 menghentikan pengembangan pada Maret 2012; proyek baru benar-benar harus menggunakan BeautifulSoup 4, selalu.

Perhatikan bahwa Anda harus membiarkan decoding HTML dari byte ke BeautifulSoup . Anda dapat memberi tahu BeautifulSoup tentang karakter yang ditemukan di header respons HTTP untuk membantu dalam decoding, tetapi ini bisa salah dan bertentangan dengan <meta>info header yang ditemukan dalam HTML itu sendiri, itulah sebabnya mengapa di atas menggunakan metode kelas internal BeautifulSoup EncodingDetector.find_declared_encoding()untuk memastikan bahwa petunjuk enkode tertanam seperti itu menang atas server yang tidak terkonfigurasi.

Dengan requests, response.encodingatribut default ke Latin-1 jika respons memiliki text/*mimetype, bahkan jika tidak ada karakter yang dikembalikan. Ini konsisten dengan HTTP RFCs tetapi menyakitkan ketika digunakan dengan parsing HTML, jadi Anda harus mengabaikan atribut itu ketika tidak charsetdiatur dalam header Tipe-Konten.

Martijn Pieters
sumber
Apakah ada sesuatu seperti StrainedSoup untuk bs4? (Saya tidak membutuhkannya sekarang tetapi hanya ingin tahu, jika ada, Anda mungkin ingin menambahkannya)
Antti Haapala
@AnttiHaapala: SoupStrainermaksud Anda? Itu tidak pergi ke mana pun, itu masih bagian dari proyek .
Martijn Pieters
Apakah ada alasan kode ini tidak lulus "fitur =" ke konstruktor BeautifulSoup? BeautifulSoup memberi saya peringatan tentang menggunakan parser default.
MikeB
1
@ MikeB: ketika saya menulis jawaban ini BeautifulSoup belum memunculkan peringatan jika Anda tidak.
Martijn Pieters
50

Orang lain merekomendasikan BeautifulSoup, tetapi jauh lebih baik menggunakan lxml . Meskipun namanya, itu juga untuk parsing dan memo HTML. Ini jauh, jauh lebih cepat daripada BeautifulSoup, dan bahkan menangani "rusak" HTML lebih baik daripada BeautifulSoup (klaim mereka untuk ketenaran). Ini memiliki API kompatibilitas untuk BeautifulSoup juga jika Anda tidak ingin mempelajari API lxml.

Ian Blicking setuju .

Tidak ada alasan untuk menggunakan BeautifulSoup lagi, kecuali Anda berada di Google App Engine atau sesuatu di mana segala sesuatu yang tidak murni Python tidak diizinkan.

lxml.html juga mendukung pemilih CSS3 sehingga hal semacam ini sepele.

Contoh dengan lxml dan xpath akan terlihat seperti ini:

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
aehlke
sumber
23
BeautifulSoup 4 akan digunakan lxmlsebagai parser default jika diinstal.
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'
Andrew Johnson
sumber
Ini memecahkan masalah yang saya miliki dengan kode saya. Terima kasih!
RJ
10

Kode berikut adalah untuk mengambil semua tautan yang tersedia di halaman web menggunakan urllib2dan 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'))
Sentient07
sumber
8

Di bawah tenda BeautifulSoup sekarang menggunakan lxml. Permintaan, lxml, & daftar pemahaman menjadikan kombo pembunuh.

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]

Dalam daftar comp, "jika '//' dan 'url.com' tidak dalam x" adalah metode sederhana untuk menggosok daftar url dari url navigasi 'internal' situs, dll.

cheekybastard
sumber
1
Jika itu adalah repost, mengapa postingan aslinya tidak termasuk: 1. meminta 2.list comp 3. logika untuk menggosok tautan internal & tautan sampah situs ?? Coba dan bandingkan hasil dari dua posting, daftar comp saya melakukan pekerjaan yang mengejutkan baik menggosok tautan sampah.
cheekybastard
OP tidak meminta fitur-fitur itu dan bagian yang dia minta sudah diposkan dan diselesaikan dengan menggunakan metode yang sama persis seperti yang Anda posting. Namun, saya akan menghapus downvote karena pemahaman daftar menambah nilai bagi orang-orang yang menginginkan fitur-fitur tersebut dan Anda secara eksplisit menyebutkannya di badan postingan. Anda juga dapat menggunakan rep :)
dotancohen
4

hanya untuk mendapatkan tautan, tanpa B.soup dan 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]

untuk operasi yang lebih kompleks, tentu saja BSoup masih lebih disukai.

ghostdog74
sumber
7
Dan jika, misalnya, ada sesuatu di antara <adan href? Katakan rel="nofollow"atau onclick="..."atau bahkan hanya baris baru? stackoverflow.com/questions/1732348/…
dimo414
apakah ada cara untuk menyaring hanya beberapa tautan dengan ini? seperti misalnya saya hanya ingin tautan yang memiliki "Episode" di tautan?
nwgat
4

Script ini melakukan apa yang Anda cari, tetapi juga menyelesaikan tautan relatif ke tautan absolut.

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
Ricky Wilson
sumber
Ini tidak melakukan apa yang harus dilakukan ti; jika resol_links () tidak memiliki root, maka itu tidak pernah mengembalikan URL apa pun.
MikeB
4

Untuk menemukan semua tautan, dalam contoh ini kita akan menggunakan modul urllib2 bersama dengan re.module * Salah satu fungsi paling kuat dalam modul re adalah "re.findall ()". Sementara re.search () digunakan untuk menemukan kecocokan pertama untuk suatu pola, re.findall () menemukan semua kecocokan dan mengembalikannya sebagai daftar string, dengan setiap string mewakili satu kecocokan *

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
Mayur Ingle
sumber
3

Mengapa tidak menggunakan ekspresi reguler:

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]))
ahmadh
sumber
1
saya ingin bisa memahami ini, di mana saya bisa secara efisien mencari tahu apa (r"<a.*?\s*href=\"(.*?)\".*?>(.*?)</a>", page)artinya Terima kasih!
user1063287
9
Ide yang sangat buruk. HTML rusak di mana-mana.
Ufoguy
2
Mengapa tidak menggunakan ekspresi reguler untuk mem-parsing html: stackoverflow.com/questions/1732348/…
allcaps
@ user1063287, web penuh dengan tutorial regex. Sepadan dengan waktu Anda untuk membaca pasangan. Meskipun RE bisa berbelit-belit, yang Anda tanyakan cukup mendasar.
alexis
3

Tautan dapat berada dalam beragam atribut sehingga Anda dapat melewati daftar atribut tersebut untuk dipilih

misalnya, dengan atribut src dan href (di sini saya menggunakan operator begin dengan ^ untuk menentukan bahwa salah satu dari nilai atribut ini dimulai dengan http. Anda dapat menyesuaikan ini sesuai kebutuhan

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)

Atribut = penyeleksi nilai

[attr ^ = nilai]

Merupakan elemen dengan nama atribut attr yang nilainya diawali (didahului) oleh nilai.

QHarr
sumber
1

Berikut ini adalah contoh menggunakan @ars jawabannya diterima dan BeautifulSoup4, requests, dan wgetmodul untuk menangani download.

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)
Blairg23
sumber
1

Saya menemukan jawaban oleh @ Blairg23 berfungsi, setelah koreksi berikut (mencakup skenario yang gagal berfungsi dengan benar):

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)

Untuk Python 3:

urllib.parse.urljoin harus digunakan untuk mendapatkan URL lengkap.

AkanKsha Bhardwaj
sumber
1

Parser BeatifulSoup sendiri bisa lambat. Mungkin lebih layak menggunakan lxml yang mampu melakukan parsing langsung dari URL (dengan beberapa batasan yang disebutkan di bawah).

import lxml.html

doc = lxml.html.parse(url)

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

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

Kode di atas akan mengembalikan tautan apa adanya, dan dalam kebanyakan kasus mereka akan berupa tautan relatif atau absolut dari root situs. Karena use case saya hanya mengekstraksi jenis tautan tertentu, di bawah ini adalah versi yang mengubah tautan ke URL lengkap dan yang secara opsional menerima pola gumpalan seperti *.mp3. Itu tidak akan menangani titik tunggal dan ganda di jalur relatif, tapi sejauh ini saya tidak membutuhkannya. Jika Anda perlu fragmen URL parsing mengandung ../atau ./kemudian urlparse.urljoin mungkin akan berguna.

CATATAN : Penguraian url lxml langsung tidak menangani pemuatan dari httpsdan tidak melakukan pengalihan, jadi untuk alasan ini versi di bawah ini menggunakan 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

Penggunaannya adalah sebagai berikut:

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"
ccpizza
sumber
lxmlhanya dapat menangani input yang valid, bagaimana cara menggantinya BeautifulSoup?
alexis
@alexis: Saya pikir lxml.htmlsedikit lebih lunak daripada lxml.etree. Jika input Anda tidak terbentuk dengan baik maka Anda dapat secara eksplisit mengatur parser BeautifulSoup: lxml.de/elementsoup.html . Dan jika Anda menggunakan BeatifulSoup maka BS3 adalah pilihan yang lebih baik.
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']
Tilak Patidar
sumber
0

Mungkin ada banyak duplikat tautan bersama dengan tautan eksternal dan internal. Untuk membedakan antara keduanya dan dapatkan tautan unik menggunakan set:

# 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)
Alexander
sumber