استرداد الروابط من صفحة الويب باستخدام 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']
وأو النسخة بيثون 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
مكتبة، والتي كما هو مكتوب ستعمل في كل من بايثون 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 م>. يمكنك إبلاغ BeautifulSoup من characterset وجدت في رؤوس استجابة HTTP للمساعدة في فك التشفير، ولكن هذا <م> يمكن م> من الخطأ ومتضاربة مع معلومات رأس <meta>
وجدت في HTML نفسه، والذي هو السبب في أن يستخدم فوق BeautifulSoup الداخلية أسلوب فئة EncodingDetector.find_declared_encoding()
للتأكد من أن هذه التلميحات ترميز جزءا لا يتجزأ من الفوز على الخادم تكوينه بشكل خاطئ.
ومع requests
، وresponse.encoding
السمة افتراضات إلى اللاتينية-1 إذا كانت الاستجابة لها MIMETYPE text/*
، حتى لو كان إرجاع أية characterset. وهذا يتفق مع المراجع HTTP ولكن المؤلم عندما تستخدم مع الاعراب HTML، لذا يجب عليك تجاهل هذه السمة عند تعيين أي charset
في رأس نوع المحتوى.
وقد أوصى آخرون بـ BeautifulSoup، لكنه أفضل بكثير للاستخدام com.lxml.على الرغم من اسمه، فهو أيضًا مخصص لتحليل وتجميع HTML.إنه أسرع بكثير من BeautifulSoup، كما أنه يتعامل مع HTML "المعطل" بشكل أفضل من BeautifulSoup (الذي يزعمون أنهم مشهورون).يحتوي على واجهة برمجة تطبيقات متوافقة مع BeautifulSoup أيضًا إذا كنت لا ترغب في تعلم واجهة برمجة تطبيقات 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
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]
في قائمة شركات، و "إذا" // "و" url.com "ليس في x" هو طريقة بسيطة لتنظيف قائمة عنوان الموقع من عناوين المواقع والملاحة "الداخلية" المواقع، الخ.
للعثور على جميع الروابط ، سنستخدم في هذا المثال وحدة 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 (هنا أنا باستخدام يبدأ مع ^ مشغل لتحديد أن أي من هذه القيم سمات يبدأ ب 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 قيمتها مسبوقة (سبق) من حيث القيمة.
اقتباس فقرة> وهنا مثال باستخدام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)
لبيثون 3:
وurllib.parse.urljoin
تمت لاستخدامها من أجل الحصول على URL الكامل بدلا من ذلك.
يمكن أن يكون المحلل اللغوي الخاص بـ BeatifulSoup بطيئًا.قد يكون أكثر جدوى للاستخدام com.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"
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']