получать ссылки с веб-страницы с помощью python и BeautifulSoup [закрыто]

144

Как я могу получить ссылки веб-страницы и скопировать URL-адрес ссылок с помощью Python?

NepUS
источник

Ответы:

196

Вот короткий фрагмент с использованием класса 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."
Внимание
30
В версии 3.2.1 BeautifulSoup нет has_attr. Вместо этого я вижу, что что-то называется, has_keyи это работает.
7
из bs4 импортируйте BeautifulSoup. (не из BeautifulSoup import BeautifulSoup ..) требуется исправление.
Ришаб Аграхари
5
Обновленный код для python3 и последней версии bs4 - gist.github.com/PandaWhoCodes/7762fac08c4ed005cec82204d7abd61b
Ашиш Чериан
71

Для полноты картины, версия 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/*MimeType, даже если CharacterSet не был возвращен. Это согласуется с HTTP RFC, но болезненно при использовании с синтаксическим анализом HTML, поэтому вам следует игнорировать этот атрибут, если charsetв заголовке Content-Type не задано значение no .

Мартейн Питерс
источник
Есть что-то вроде StrainedSoup для bs4? (Мне это сейчас не нужно, но просто интересно, если бы вы могли добавить это)
Антти Хаапала
@AnttiHaapala: SoupStrainerты имеешь в виду? Это никуда не делось, это все еще часть проекта .
Мартейн Питерс
Есть ли причина, по которой этот код не передает "features =" конструктору BeautifulSoup? BeautifulSoup выдает предупреждение об использовании парсера по умолчанию.
MikeB
1
@MikeB: когда я писал этот ответ, BeautifulSoup еще не вызывал предупреждения, если вы этого не сделали.
Мартейн Питерс
51

Другие рекомендовали BeautifulSoup, но гораздо лучше использовать lxml . Несмотря на название, он также предназначен для синтаксического анализа и очистки HTML. Он намного, намного быстрее, чем BeautifulSoup, и даже обрабатывает «сломанный» HTML лучше, чем BeautifulSoup (их претензия на известность). У него также есть API совместимости для BeautifulSoup, если вы не хотите изучать lxml API.

Ян Бликинг соглашается .

Больше нет причин использовать 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
Aehlke
источник
24
BeautifulSoup 4 будет использоваться lxmlв качестве анализатора по умолчанию, если он установлен.
Мартейн Питерс
30
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'
Эндрю Джонсон
источник
1
Это решило мою проблему с кодом. Спасибо!
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'))
Sentient07
источник
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' not in x" - это простой метод очистки списка URL-адресов «внутренних» навигационных URL-адресов сайтов и т. Д.

нахальный ублюдок
источник
1
Если это репост, то почему исходный пост не включает: 1. запросы 2. составление списка 3. логику очистки внутренних и нежелательных ссылок на сайте ?? Попробуйте сравнить результаты двух сообщений, мой список списков на удивление хорошо очищает ненужные ссылки.
cheekybastard
OP не запрашивал эти функции, и часть, которую он просил, уже была опубликована и решена с использованием того же метода, что и вы. Тем не менее, я удалю отрицательный голос, поскольку понимание списка действительно увеличивает ценность для людей, которым действительно нужны эти функции, и вы явно упоминаете их в теле сообщения. Кроме того, вы можете использовать репутацию :)
dotancohen
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.

призрачная собака74
источник
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
Рики Уилсон
источник
Это не делает того, что он должен делать; если у resolve_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 06
9
Действительно плохая идея. Повсюду сломанный HTML.
Ufoguy
2
Почему бы не использовать регулярные выражения для синтаксического анализа html: stackoverflow.com/questions/1732348/…
allcaps
@ user1063287, в сети полно руководств по регулярным выражениям. Это стоит вашего времени, чтобы прочитать пару. Хотя RE могут быть очень запутанными, вопрос, о котором вы спрашиваете, довольно прост.
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, значение которого имеет префикс (предваряется) значением.

QHarr
источник
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)
Blairg23
источник
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"
ccpizza
источник
lxmlможет обрабатывать только действительный ввод, как его заменить BeautifulSoup?
Alexis
@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)
Александр
источник