извлекать ссылки с веб-страницы с помощью python и BeautifulSoup
-
22-08-2019 - |
Вопрос
Как я могу получить ссылки на веб-страницу и скопировать URL-адрес ссылок с помощью Python?
Решение
Вот короткий фрагмент с использованием класса SoupStrainer в BeautifulSoup:
import httplib2
from BeautifulSoup 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 на самом деле довольно хороша и охватывает ряд типичных сценариев:
http://www.crummy.com/software/BeautifulSoup/documentation.html
Редактировать:Обратите внимание, что я использовал класс SoupStrainer, потому что он немного эффективнее (с точки зрения памяти и скорости), если вы заранее знаете, что анализируете.
Другие советы
Для полноты картины, версия BeautifulSoup 4, также использующая кодировку, предоставляемую сервером:
from bs4 import BeautifulSoup
import urllib2
resp = urllib2.urlopen("http://www.gpsbasecamp.com/national-parks")
soup = BeautifulSoup(resp, from_encoding=resp.info().getparam('charset'))
for link in soup.find_all('a', href=True):
print link['href']
или версия Python 3:
from bs4 import BeautifulSoup
import urllib.request
resp = urllib.request.urlopen("http://www.gpsbasecamp.com/national-parks")
soup = BeautifulSoup(resp, from_encoding=resp.info().get_param('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
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, 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 о наборе символов, найденном в заголовках HTTP-ответа, чтобы помочь в декодировании, но это может ошибаться и вступать в конфликт с <meta>
информация о заголовке содержится в самом HTML, именно поэтому в приведенном выше примере используется метод внутреннего класса BeautifulSoup EncodingDetector.find_declared_encoding()
чтобы убедиться, что такие встроенные подсказки кодирования побеждают неправильно сконфигурированный сервер.
С requests
, тот response.encoding
атрибут по умолчанию имеет значение Latin-1, если ответ содержит text/*
mimetype, даже если набор символов не был возвращен.Это согласуется с HTTP RFC, но вызывает дискомфорт при использовании с синтаксическим анализом HTML, поэтому вам следует игнорировать этот атрибут, когда нет charset
задается в заголовке Content-Type.
Другие рекомендовали 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
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'
Следующий код предназначен для извлечения всех ссылок, доступных на веб-странице, с помощью 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'))
Под капотом 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-адресов навигации сайтов и т.д.
Чтобы найти все ссылки, мы будем в этом примере использовать модуль 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
просто для получения ссылок, без 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.
Почему бы не использовать регулярные выражения:
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]))
Этот скрипт делает то, что вы ищете, но также преобразует относительные ссылки в абсолютные.
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
Ссылки могут находиться внутри различных атрибутов, поэтому вы можете передать список этих атрибутов для выбора
например, с помощью атрибута src и href (здесь я использую оператор starts with ^, чтобы указать, что любое из значений этих атрибутов начинается с 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, значение которого имеет префикс (перед которым) value.
Вот пример использования принятого ответа @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 работает, после следующего исправления (охватывающего сценарий, в котором он не сработал корректно):
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-адрес.
Собственный анализатор 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-адреса и которая необязательно принимает шаблон glob, такой как *.mp3
.Однако он не будет обрабатывать одиночные и двойные точки в относительных путях, но до сих пор у меня не было в этом необходимости.Если вам нужно разобрать фрагменты URL, содержащие ../
или ./
тогда urlparse.urljoin -url-адрес может пригодиться.
ПРИМЕЧАНИЕ:Прямой синтаксический анализ 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"
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']