отримати посилання з веб-сторінки за допомогою 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, використання сушильного ситечка є чудовою ідеєю, оскільки воно дозволяє обійти багато непотрібного розбору, коли все, що вам потрібно, - це посилання.
Еван Фосмарк

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 ..) потрібна корекція.
Рішабх Аграхарі

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атрибут за замовчуванням до Latin-1, якщо відповідь має text/*міметик, навіть якщо жоден набір символів не повертається. Це узгоджується з HTTP RFC, але болісно при використанні з розбором HTML, тому слід ігнорувати цей атрибут, коли charsetв заголовку Content-Type не встановлено жодне .


Чи є щось на зразок StrainedSoup для bs4? (Мені це зараз не потрібно, але просто цікаво, чи є ви, можливо, ви хочете додати це)
Antti Haapala

@AnttiHaapala: SoupStrainerти маєш на увазі? Він нікуди не дійшов, він все ще є частиною проекту .
Martijn Pieters

Чи є причина, що цей код не передає "особливості =" конструктору BeautifulSoup? BeautifulSoup дає мені попередження про використання аналізатора за замовчуванням.
MikeB

1
@MikeB: коли я написав цю відповідь, BeautifulSoup ще не підняв попередження, якщо ви цього не зробили.
Martijn Pieters

50

Інші рекомендують BeautifulSoup, але набагато краще використовувати lxml . Незважаючи на свою назву, він також призначений для розбору та скребки HTML. Це набагато, набагато швидше, ніж BeautifulSoup, і він навіть обробляє "зламаний" HTML краще, ніж BeautifulSoup (їх претензія на славу). Він також має API сумісності для BeautifulSoup, якщо ви не хочете вивчати API lxml.

Ян Блікінг погоджується .

Немає жодної причини використовувати 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

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]

У списку comp "if" // "та" url.com "not in x" - це простий метод для скрупування списку URL-адрес "внутрішніх" навігаційних сайтів тощо.


1
Якщо це репост, чому оригінальна публікація не включає: 1. запити 2.list comp 3. логіка для скрупування внутрішніх та непотрібних посилань ?? Спробуйте і порівняйте результати двох публікацій, мій список складає напрочуд гарну роботу, очищаючи непотрібні посилання.
чіпкий джем

ОП не вимагав цих функцій, а частина, яку він просив, вже була розміщена та вирішена, використовуючи той самий метод, що і ви. Однак я видалю підсумки, оскільки розуміння списку додає значення людям, які хочуть цих функцій, і ви чітко згадуєте їх у тілі публікації. Також ви можете скористатися
респіратором

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

Це не робить те, що малося робити ти; якщо res_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)означає? Дякую!
користувач1063287

9
Дійсно погана ідея. Повсякчас зламаний HTML.
Уфогуй

2
Чому б не використати регулярні вирази для розбору HTML: stackoverflow.com/questions/1732348 / ...
allcaps

@ user1063287, в Інтернеті повно навчальних посібників з регулярними виразками. Варто витратити свій час, щоб прочитати пару. Незважаючи на те, що РЕ може отримати справжню суперечку, той, про кого ви питаєте, є досить простим.
alexis

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, може стати в нагоді.

ПРИМІТКА : Прямий розбір URL-адреси lxml не обробляє завантаження з 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"

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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.