从本章开始,我们将探索与刮片相关的工具和技术,同时我们还将部署一些刮片代码。与 web 探索、Python 库、元素识别和遍历相关的特性是我们迄今为止学习的主要概念。
网页抓取通常是一个具有挑战性的长期过程,需要了解网站的运行情况。了解和识别用于构建网站的后端或工具的基本能力将有助于完成任何爬取任务。这也与称为逆向工程的过程有关。有关此类工具的更多信息,请参考第 3 章、使用 LXML、XPath 和 CSS 选择器以及使用 web 浏览器开发工具访问 web 内容部分。除此之外,还需要识别用于遍历和操作 HTML 标记等元素的工具,pyquery就是其中之一。
在前面的章节中,我们探讨了 XPath、CSS 选择器和 LXML。在本章中,我们将探讨如何使用pyquery,它具有类似 jQuery 的功能,似乎更高效,因此在涉及到 web 抓取过程时更容易处理。
在本章中,您将了解以下主题:
pyquery简介- 探索
pyquery(主要方法和属性) - 使用
pyquery爬取
本章要求使用网络浏览器(Google Chrome 或 Mozilla Firefox)。我们将使用以下 Python 库:
pyqueryurllibrequests
如果您当前的 Python 设置中不存在这些库,请参阅第 2 章、Python 和 Web–使用 urllib 和请求以及设置内容部分,以获取安装和设置帮助。
本章的代码文件可在本书的 GitHub 存储库中找到:https://github.com/PacktPublishing/Hands-On-Web-Scraping-with-Python/tree/master/Chapter04 。
pyquery是一个类似 jQuery 的 Python 库,它使用lxml库。这为处理标记元素提供了一个简单的交互式环境,用于操作和遍历目的。
pyquery expressions are also similar to jquery, and users with jquery knowledge will find it more convenient to use in Python.
顾名思义,pyqueryPython 库增强了与 XML 和 HTML 中元素相关的query编写过程。pyquery缩短了元素处理,并提供了一种更具洞察力的脚本编写方法,适用于抓取和基于 DOM 的遍历和操作任务。
pyquery表达式使用 CSS 选择器执行查询,以及它实现的其他功能。例如,pyquery使用以下表达式:
page.find('a').attr('href') -- (pyquery expression) cssselect使用以下表达式:
cssselect('a').get('href') -- (cssselect expression) jQuery (write less, do more) is one of the most admired JavaScript libraries and is small, quick, and has lots of features that support DOM/HTML/CSS, and more. Web document-based traversing, manipulation, event handling, animation, AJAX, and more are some of its main features. Please visit https://jquery.com/ for more information. For more information on pyquery and its documentation, please visit https://pythonhosted.org/pyquery/ or https://github.com/gawel/pyquery/.
在我们继续探索pyquery及其功能之前,让我们先使用pip安装它:
C:\> pip install pyqueryFor more information on using pip and library installation, please refer to the Setting things up section in Chapter 2, Python and the Web – Using urllib and Requests.
使用pip成功安装pyquery时,将安装以下库:
cssselect-1.0.3lxml-4.3.1pyquery-1.4.0
>>> in the code represents the use of the Python IDE; it accepts the code or instructions and displays the output on the next line.
一旦安装完成并成功,我们可以使用pyquery,如下代码所示,来确认设置。我们可以使用dir()函数来探索它包含的属性:
>>> from pyquery import PyQuery as pq
>>> print(dir(pq))
['Fn', '__add__', '__call__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '_filter_only', '_get_root', '_next_all', '_prev_all', '_translator_class', '_traverse','addClass', 'add_class', 'after', 'append', 'appendTo', 'append_to','attr','base_url','before','children', 'clear', 'clone', 'closest', 'contents', 'copy', 'count', 'css','each','empty', 'encoding','end','eq', 'extend', 'filter', 'find','fn','hasClass','has_class','height','hide', 'html', 'index','insert','insertAfter', 'insertBefore', 'insert_after','insert_before', 'is_', 'items', 'length','make_links_absolute',
'map','next','nextAll','next_all','not_','outerHtml','outer_html','parent','parents', 'pop', 'prepend', 'prependTo', 'prepend_to','prev', 'prevAll', 'prev_all', 'remove', 'removeAttr', 'removeClass', 'remove_attr', 'remove_class','remove_namespaces', 'replaceAll', 'replaceWith', 'replace_all', 'replace_with', 'reverse', 'root','show', siblings','size','sort','text', 'toggleClass', 'toggle_class', 'val', 'width', 'wrap', 'wrapAll','wrap_all','xhtml_to_html']现在,我们将探索pyquery中与爬取概念相关的某些特性。为此,我们将使用可从获得的页面源 https://www.python.org 已在本地保存为test.html以提供真实可用性:
Page source obtained from https://www.python.org In Google Chrome, you can right-click on the web page and choose the View page source menu option or press Ctrl + U to obtain the page source.
但是,仅仅获取页面源代码或 HTML 代码是不够的,因为我们需要将这些内容加载到库中,以获得更多的工具来进行探索。我们将在下一节中进行介绍。
While testing or following the code, you might find or require changes to be done on the pyquery code expressions in order to obtain the real output. Page sources that are obtained now might be updated or changed. You are suggested to obtain the latest page source from the source URL (https://www.python.org).
在大多数情况下,文档内容通过使用requests或urllib获得,并提供给pyquery如下:
>>> from pyquery import PyQuery as pq
>>> import requests
>>> response = requests.get('http://www.example.com').text #content
>>> from urllib.request import urlopen
>>> response = urlopen('http://www.example.com').read()
>>> docTree = pq(response)pyquery还可以使用 Python 库urllib(默认)或请求加载 URL。它还支持基于请求的参数:
>>> pq("https://www.python.org")
[<html.no-js>]
>>> site=pq("https://www.python.org")
>>> print(type(site))
<class 'pyquery.pyquery.PyQuery'>
>>> pq("https://www.samsclub.com")
[<html>]我们从前面代码中获得的pq对象正在使用lxml提供的 XML 解析器(默认)进行解析,该解析器也可以通过传递给它的额外parser参数进行更新:
>>> doc = pq('http://www.exaple.com', parser = 'xml') #using parser xml
>>> doc = pq('http://www.exaple.com', parser = 'html') #using parser html通常,来自页面源或其他源(如文件)的 HTML 代码作为字符串提供给pyquery进行进一步处理,如下代码所示:
>>> doc = pq('<div><p>Testing block</p><p>Second block</p></div>')
>>> print(type(doc))
<class 'pyquery.pyquery.PyQuery'>
>>> pagesource = open('test.html','r').read() #reading locally saved HTML
>>> print(type(pagesource))
<class 'str'>
>>> page = pq(pagesource)
>>> print(type(page))
<class 'pyquery.pyquery.PyQuery'>通过从加载的文档或 URL 接收的PyQuery对象或pq,我们可以继续探索pyquery提供的功能。
pyquery拥有大量的属性和方法,可以部署这些属性和方法来获取所需的内容。在以下示例中,我们将根据本节中的代码确定实现:
>>> page('title') #find element <title>
[<title>]
>>> page.find('title').text() #find element <title> and return text content
'Welcome to Python.org'
>>> page.find('meta[name="description"]').attr('content')
'The official home of the Python Programming Language'
>>> page.find('meta[name="keywords"]').attr('content')
'Python programming language object oriented web free open source software license documentation download community'
>>> buttons = page('a.button').html() #return HTML content for element <a> with class='button'
>>> buttons
'>_\n <span class="message">Launch Interactive Shell</span>\n ' 以下是它们的一些功能,以及可以在前面的代码中看到的描述:
find():使用 CSS 选择器搜索提供的元素或计算查询表达式构建text():以字符串形式返回元素内容attr():标识属性并返回其内容html():返回计算表达式的 HTML 内容
The class and id CSS attributes are represented with . and #, respectively, and are prefixed to the attribute's value. For example, <a class="main" id="mainLink"> will be identified as a.main and a#mainLink.
在下面的代码中,我们列出了所有已识别的具有class属性和menu值的<ul>元素:
>>> page('ul.menu') #<ul> element with attribute class='menu'
[<ul.menu>, <ul.navigation.menu>, <ul.subnav.menu>, <ul.navigation.menu>, <ul.subnav.menu>, <ul.navigation.menu>,.............., <ul.subnav.menu>, <ul.footer- links.navigation.menu.do-not-print>] 表达式被传递给 PyQuery 对象,该对象生成一个已计算元素的列表。对这些元素进行迭代以获取其精确值或内容。
PyQuery 还包含伪类或:pseudo element,用于索引和获取预定义的表达式结果。:pseudo element也可以附加到现有选择器查询。以下代码实现了遍历时常见的一些伪元素:
>>> page('nav:first') #first <nav> element
[<nav.meta-navigation.container>]
>>> page('a:first') #first <a> element
[<a>]
>>> page('ul:first') #first <ul> element
[<ul.menu>]
>>> page('ul:last') #last <ul> element
[<ul.footer-links.navigation.menu.do-not-print>]让我们看一下前面代码中使用的伪元素:
:first:返回所提供内容中第一次出现的元素:last:返回所提供内容中最后出现的元素
让我们看看更多:pseudo element的一般实现,以列出 HTML 元素:
>>> page(':header') #header elements found
[<h1.site-headline>, <h1>, <h1>, <h1>, <h1>, <h1>, <h2.widget-title>, <h2.widget-title>..........,<h2.widget-title>, <h2.widget-title>, <h2.widget-title>]
>>> page(':input') #input elements found
[<input#id-search-field.search-field>, <button#submit.search-button>]
>>> page(':empty') #empty elements found
[<meta>, <meta>, <link>, <meta>, <meta>, <meta>, <meta>,<script>, <link>, <link>,........,<img.python-logo>, <span.icon-search>,<span.icon-facebook>, <span.icon-twitter>, <span.icon-freenode>, ...........,<span.icon-feed>, <div.python-logo>, <span#python-status-indicator.python
-status-indicator-default>, <script>, <script>, <script>]
>>> page(':empty:odd') #empty elements, only Odd ones are listed
[<meta>, <meta>, <meta>, <meta>, <meta>, <meta>, <script>, <link>, <link>, <link>, <link>, <meta>, .......,<img.python-logo>, <span.icon-google-plus>, <span.icon-twitter>, <span.breaker>, <span.icon-download>, <span.icon-jobs>, <span.icon-calendar>, <span.icon-python>, <div.python-logo>, <script>,<script>]下面是我们在前面代码中使用的:pseudo element:
-
:header:返回在页面中找到的标题元素(h1、h2、…、h5、h6)。 -
:input:返回所有输入元素。存在大量基于 HTML<form>的伪元素。请参考https://pythonhosted.org/pyquery/ 了解更多信息。 -
:empty:返回所有没有子元素的元素。 -
:odd:返回索引为奇数的元素。它们可以与其他:pseudo element一起使用,如:empty:odd。 -
:even:与:odd类似,但返回索引均匀的元素。
下面的代码演示了一起遍历:pseudo element和元素属性的表达式:
>>> page.find('ul:first').attr('class') #class name of first <ul> element
'menu'
>>> page.find('a:first').attr('href') #href value of first <a> element
'#content'
>>> page.find('a:last').attr('href') #href value of last <a> element
'/psf/sponsorship/sponsors/'
>>> page.find('a:eq(0)').attr('href') #href value of first <a> element using Index!
'#content'
>>> page.find('a:eq(0)').text() #text from first <a> element
'Skip to content' 以下是更多的:pseudo element。我们可以使用这些来解决元素的index:
:eq:选择特定的索引号;计算结果为equals to。:lt:提供的索引号计算为less than。例如,page('a:lt(2)')。:gt:提供的索引号评估为greater than。例如,page('a:gt(0)')。
除了用于识别索引和查找元素的一般功能外,:pseudo element还可以使用提供的文本搜索元素,如下代码所示:
>>> page('p:contains("Python")') #return elements <p> with text 'Python"
[<p>, <p>, <p>, <p>, <p>, <p>, <p>, <p>, <p>, <p>, <p>, <p>, <p>, <p>]
>>> page('p:contains("python.org")') #return elements <p> with text "python.org"
[<p>, <p>]
#return text from second <p> element containing text "python.org"
>>> page('p:contains("python.org")').eq(1).text()
'jobs.python.org'以下列表描述了前面代码中使用的:contains和eq()的简单定义:
:contains:匹配包含所提供文本的所有元素。eq():返回为特定索引编号找到的元素。评估为equals to,与:eq类似。
pyquery有几个返回布尔答案的函数,在需要搜索具有属性的元素并确认属性值的情况下非常有效:
#check if class is 'python-logo'
>>> page('h1.site-headline:first a img').is_('.python-logo')
*True*
#check if <img> has class 'python-logo'
>>> page('h1.site-headline:first a img').has_class('python-logo')
*True* 以下是以前代码中使用的函数及其定义:
is_():接受选择器作为参数,如果选择器匹配元素,则返回True,否则返回False。has_class():如果选择器与提供的类匹配,则返回True。它对于识别具有class属性的元素非常有用。
我们在pyquery中使用了一些重要的函数和工具,以增强元素标识和遍历相关属性。在下一节中,我们将学习并演示迭代。
在本节中,我们将演示pyquery提供的迭代(重复执行)功能。它在许多情况下都是有效且易于处理的。
在下面的代码中,我们正在搜索在包含单词Python.org的<meta>标记中找到的name和property属性。我们还使用 Python 的List Comprehension技术来演示单行编码功能:
#Find <meta> with attribute 'content' containing '..Python.org..'
#and list the attribute 'name' that satisfies the find()
>>> meta=page.find('meta[content*="Python.org"]')
>>> [item.attr('name') for item in meta.items() if item.attr('name') is not None]
['application-name', 'apple-mobile-web-app-title']
#Continuing from code above list value for attribute 'property'
>>> [item.attr('property') for item in meta.items() if item.attr('property') is not None]
['og:site_name', 'og:title']正如我们在前面的代码中所看到的,我们在循环中使用items()函数和元素 meta 来迭代所提供的选项。可使用items()探索产生可编辑对象的表达式。返回None的结果不在列表中:
>>> social = page.find('a:contains("Socialize") + ul.subnav li a')
>>> [item.text() for item in social.items() if item.text() is not None]
['Google+', 'Facebook', 'Twitter', 'Chat on IRC']
>>> [item.attr('href') for item in social.items() if item.attr('href') is not None]
['https://plus.google.com/+Python', 'https://www.facebook.com/pythonlang?fref=ts', 'https://twitter.com/ThePSF', '/community/irc/']
>>> webdevs = page.find('div.applications-widget:first ul.menu li:contains("Web Development") a')
>>> [item.text() for item in webdevs.items() if item.text() is not None]
['Django', 'Pyramid', 'Bottle', 'Tornado', 'Flask', 'web2py'] 在前面的代码中,pyquery对象收集社交和 web 开发部分提供的名称和链接。这些可以在使用 Python for。。。在下面的屏幕截图中。使用 Python 列表理解技术迭代对象:
Upcoming events to be extracted using pyquery
在下面的代码中,我们将探索从upcomingevents迭代中检索到的更多细节:
>>> eventsList = []
>>> upcomingevents = page.find('div.event-widget ul.menu li')
>>> for event in upcomingevents.items():
... time = event.find('time').text()
... url = event.find('a[href*="events/python"]').attr('href')
... title = event.find('a[href*="events/python"]').text()
... eventsList.append([time,title,url])
...
>>> eventsListeventsList包含从即将到来的事件中提取的细节,如前面的屏幕截图所示。此处提供eventsList的输出:
[['2019-02-19', 'PyCon Namibia 2019', '/events/python-events/790/'], ['2019-02-23', 'PyCascades 2019', '/events/python-events/757/'],
['2019-02-23', 'PyCon APAC 2019', '/events/python-events/807/'], ['2019-02-23', 'Berlin Python Pizza', '/events/python-events/798/'],
['2019-03-15', 'Django Girls Rivers 2019 Workshop', '/events/python-user-group/816/']]DevTools can be used to identify a CSS selector for the particular section and can be further processed with the looping facility. For more information regarding the CSS Selector, please refer to Chapter 3, Using LXML, XPath, and CSS Selectors, and the XPath and CSS selectors using DevTools section.
下面的代码举例说明了通过使用find()和items()的pyquery迭代过程:
>>> buttons = page.find('a.button')
>>> for item in buttons.items():
... print(item.text(),' :: ',item.attr('href'))
...
>_ Launch Interactive Shell :: /shell/
Become a Member :: /users/membership/
Donate to the PSF :: /psf/donations/
>>> buttons = page.find('a.button:odd')
>>> for item in buttons.items():
... print(item.text(),' :: ',item.attr('href'))
...
Become a Member :: /users/membership/
>>> buttons = page.find('a.button:even')
>>> for item in buttons.items():
... print(item.text(),' :: ',item.attr('href'))
...
>_ Launch Interactive Shell :: /shell/
Donate to the PSF :: /psf/donations/有关pyquery中的特性、属性和方法的更多信息,请参考https://pythonhosted.org/pyquery/index.html 。
在上一节中,我们学习了如何使用pyquery中提供的一些重要特性,并使用这些特性遍历或识别元素。在本节中,我们将使用pyquery中的大部分功能,并通过提供各种用例的示例,使用它们从 web 上获取数据。
在本例中,我们将从中删除数据科学类别中的公告相关详细信息 https://developer.ibm.com/announcements/ 类别/数据科学/。
The same URL from https://developer.ibm.com/ has also been used to collect data using lxml.cssselect under Example 3, in the Web scraping using LXML section from Chapter 3, Using LXML, XPath, and CSS Selectors. It is suggested that you explore both examples and compare the features that were used.
首先,我们导入pyquery和requests:
from pyquery import PyQuery as pq
import requests
dataSet = list ()创建dataSet以便您有一个空列表来收集我们将从各个页面中找到的数据,以及要使用的库。我们已经声明了read_url(),它将用于读取提供的 URL 并返回PyQuery对象。在本例中,我们将使用sourceUrl,即https://developer.ibm.com/announcements/ :
sourceUrl= 'https://developer.ibm.com/announcements/'
def read_url(url):
"""Read given Url , Returns pyquery object for page content"""
pageSource = requests.get(url).content
return pq(pageSource)要收集的信息可从中检索 https://developer.ibm.com/announcements/category/data-science/?fa=date:DESC &fb=或使用sourceUrl+"category/data-science/?fa=date:DESC&fb="获取。在这里,我们将通过pageUrls进行循环。
pageUrls产生以下页面 URL。这些是通过列表理解和range()获得的:
- https://developer.ibm.com/announcements/category/data-science/page/1?fa=date:DESC &fb=
- https://developer.ibm.com/announcements/category/data-science/page/2?fa=date:DESC &fb=
如下面的代码所示,pageUrls生成一个基于页面的 URL 列表,可以通过使用get_details()函数进一步处理这些 URL。这用于检索文章:
if __name__ == '__main__' :
mainUrl = sourceUrl+ "category/data-science/?fa=date:DESC&fb="
pageUrls = [sourceUrl+ "category/data-science/page/%(page)s?fa=date:DESC&fb=" % { 'page' : page} for page in range ( 1 , 3 )]
for pages in pageUrls:
get_details(pages)
print ( "\nTotal articles collected: " , len (dataSet))
print (dataSet)从前面的代码中可以看出,列出了以下 URL:
- https://developer.ibm.com/announcements/category/data-science/page/1?fa=date:DESC &fb=
- https://developer.ibm.com/announcements/category/data-science/page/2?fa=date:DESC &fb=
pageUrls中的 URL 被迭代并传递给get_details()进行进一步处理,如下代码所示:
def get_details(page):
"""read 'page' url and append list of queried items to dataSet"""
response = read_url(page)
articles = response.find( '.ibm--card > a.ibm--card__block_link' )
print ( "\nTotal articles found :" , articles. __len__ (), ' in Page: ' , page)
for article in articles.items():
link = article.attr( 'href' )
articlebody = article.find( 'div.ibm--card__body' )
adate = articlebody.find( 'h5 > .ibm--card__date' ).text()
articlebody.find( 'h5 > .ibm--card__date' ).remove()
atype = articlebody.find( 'h5' ).text().strip()
title = articlebody.find( 'h3.ibm--card__title' ).text().encode( 'utf-8' )
excerpt = articlebody.find( 'p.ibm--card__excerpt' ).text().encode( 'utf-8' )
category = article.find( 'div.ibm--card__bottom > p.cpt-byline__categories span' )
if link:
link = str (link).replace( '/announcements/' , mainUrl)
categories = [span.text for span in category if span.text != '+' ]
dataSet.append([link, atype, adate, title, excerpt, "," .join(categories)])read_url()读取传递给get_details()的页面 URL,并从PyQuery对象获取response。使用 CSS 选择器将包含块的信息标识为项目。因为有不止一个articles迭代可用,所以我们使用items()。然后在清理、替换和合并活动的帮助下对单个数据元素进行处理,然后再将其附加到主数据集,在本例中为dataSet。PyQuery 表达式也可以通过使用articlebody来缩短。
另外,使用remove()``PyQuery(操作)方法移除<h5>中的.ibm--card__date,以获得atype。atype内容还将包含额外的.ibm--card__date详细信息,如果使用时未删除以下代码:
articlebody.find( 'h5 > .ibm--card__date' ).remove())从前面的代码中获得的最终输出如下:
Total articles found : 8 in Page: https://developer.ibm.com/announcements/category/data-science/page/1?fa=date:DESC&fb=
Total articles found : 2 in Page: https://developer.ibm.com/announcements/category/data-science/page/2?fa=date:DESC&fb=
Total articles collected: 10
[['https://developer.ibm.com/announcements/model-mgmt-on-watson-studio-local/', 'Announcement', 'Nov 05, 2018', b'Perform feature engineering and model scoring', b'This code pattern demonstrates how data scientists can leverage IBM Watson Studio Local to automate the building and training of\xe2\x80\xa6', 'Analytics,Apache Spark'], ..........................., ['https://developer.ibm.com/announcements/algorithm-that-gives-you-answer-to-any-particular-question-based-on-mining-documents/', 'Announcement', 'Sep 17, 2018', b'Query a knowledge base to get insights about data', b'Learn a strategy to query a knowledge graph with a question and find the right answer.', 'Artificial Intelligence,Data Science'], ['https://developer.ibm.com/announcements/build-a-domain-specific-knowledge-graph-from-given-set-of-documents/', 'Announcement', 'Sep 14, 2018', b'Walk through the process of building a knowledge base by mining information stored in the documents', b'Take a look at all of the aspects of building a domain-specific knowledge graph.', 'Artificial Intelligence,Data Science']]在本例中,我们将从的书籍中获取引用的详细信息 http://quotes.toscrape.com/tag/books/ 。每个单独的报价都包含某些信息,以及指向作者详细信息页面的链接,我们还将对其进行处理,以便获得有关作者的信息:
Main page from http://quotes.toscrape.com/tag/books/
在下面的代码中,keys中的元素将用作输出键,并包含 Python 字典。基本上,我们将收集键中元素的数据:
from pyquery import PyQuery as pq
sourceUrl = 'http://quotes.toscrape.com/tag/books/'
dataSet = list ()
keys = [ 'quote_tags' , 'author_url' , 'author_name' , 'born_date' , 'born_location' , 'quote_title' ]
def read_url(url):
"""Read given Url , Returns pyquery object for page content"""
pageSource = pq(url)
return pq(pageSource)前面代码中的read_url()也进行了更新,与示例 1–抓取数据科学公告部分中使用的库不同。在本例中,它返回所提供 URL 的 PyQuery 对象:
if __name__ == '__main__' :
get_details(sourceUrl)
print ( " \n Total Quotes collected: " , len (dataSet))
print (dataSet)
for info in dataSet:
print (info[ 'author_name' ], ' born on ' ,info[ 'born_date' ], ' in ' ,info[ 'born_location' ])对于info字典中的某些值,dataSet正在进行额外的迭代,该字典位于dataSet中。
如下代码所示,get_details()使用while循环进行分页,并由nextPage值控制:
def get_details(page):
"""read 'page' url and append list of queried items to dataSet"""
nextPage = True
pageNo = 1
while (nextPage):
response = read_url(page + 'page/' + str (pageNo))
if response.find( "ul.pager:has('li.next')" ):
nextPage = True
else :
nextPage = False
quotes = response.find( '.quote' )
print ( " \n Total Quotes found :" , quotes. __len__ (), ' in Page: ' , pageNo)
for quote in quotes.items():
title = quote.find( '[itemprop="text"]:first' ).text()
author = quote.find( '[itemprop="author"]:first' ).text()
authorLink = quote.find( 'a[href*="/author/"]:first' ).attr( 'href' )
tags = quote.find( '.tags [itemprop="keywords"]' ).attr( 'content' )
if authorLink:
authorLink = 'http://quotes.toscrape.com' + authorLink
linkDetail = read_url(authorLink)
born_date = linkDetail.find( '.author-born-date' ).text()
born_location = linkDetail.find( '.author-born-location' ).text()
if born_location.startswith( 'in' ):
born_location = born_location.replace( 'in ' , '' )
dataSet.append( dict ( zip (keys,[tags,authorLink,author,born_date,born_location,title[ 0 : 50 ]])))
pageNo += 1 :has()返回与传递给它的选择器匹配的元素。在本例中,我们正在确认pager类是否有next类的<li>元素,即ul.pager:has('li.next')。如果表达式为true,则另一个页面存在页面链接,else终止循环。
使用items()对获取的quotes进行迭代,得到title、author、tags、authorLink。使用read_url()函数进一步处理authorLinkURL,以便分别从born_date和born_location的.author-born-date和.author-born-location类中获取与作者相关的特定信息。
我们在前面代码中使用的元素类可以在 Page Source 中找到,如以下屏幕截图所示:
Inner page with author details
zip()Python 函数与键和引号字段一起使用,作为 Python 字典附加到dataSet之后。
上述代码的输出如下所示:
Total Quotes found : 10 in Page: 1
Total Quotes found : 1 in Page: 2
Total Quotes collected: 11
[{'author_name': 'Jane Austen', 'born_location': 'Steventon Rectory, Hampshire, The United Kingdom', 'quote_tags': 'aliteracy,books,classic,humor', 'author_url': 'http://quotes.toscrape.com/author/Jane-Austen', 'quote_title': '“............................... ', 'born_date': 'December 16, 1775'},
{'author_name': 'Mark Twain', 'born_location': 'Florida, Missouri, The United States', 'quote_tags': 'books,contentment,friends,friendship,life', 'author_url': 'http://quotes.toscrape.com/author/Mark-Twain', 'quote_title': '“.........................................', 'born_date': 'November 30, 1835'}
,...................................................................................................,
{'author_name': 'George R.R. Martin', 'born_location': 'Bayonne, New Jersey, The United States', 'quote_tags': 'books,mind', 'author_url': 'http://quotes.toscrape.com/author/George-R-R-Martin', 'quote_title': '“... ...................................', 'born_date': 'September 20, 1948'}]为获得的dataSet运行了一个额外的循环,结果是一个字符串,如下所示:
Jane Austen born on December 16, 1775 in Steventon Rectory, Hampshire, The United Kingdom
Mark Twain born on November 30, 1835 in Florida, Missouri, The United States
............................
............................
George R.R. Martin born on September 20, 1948 in Bayonne, New Jersey, The United States在本例中,我们将从美国曲棍球联盟(AHL)季后赛结果中提取数据,这些数据可从中获得 http://www.flyershistory.com/cgi-bin/ml-poffs.cgi :
AHL Playoff results
前面的 URL 包含 AHL 的季后赛结果。本页以表格形式显示有关结果的信息。页面源中显示相关信息的部分如以下屏幕截图所示:
Page source from http://www.flyershistory.com/cgi-bin/ml-poffs.cgi The preceding screenshot contains the top and bottom part of the tabular information from the source URL and presents two different formats of <tr> that are available in the page source. The number of <td> that are available in <tr> have different, extra information.
分析源格式后,还需要指出,包含所需值的<td>没有可用于标识特定表单元格的属性。在这种情况下,我们可以使用 CSS 选择器,即p seudo 选择器,例如td:eq(0)或td:eq(1),来定位<td>或数据单元格的位置。
For more information on CSS selectors, please visit Chapter 3, Using LXML, XPath, and CSS Selectors, the Introduction to XPath and CSS selector section, in the CSS Selectors and Pseudo Selectors sub-section.
由于本例将使用pyquery,因此我们将使用eq()方法,该方法接受索引并返回元素。例如,我们可以对选择的 PyQuery 对象tr使用tr.find( 'td' ).eq( 1 ).text(),搜索索引等于1的元素td,即<td>,并返回该元素的文本。
在此,我们有兴趣收集keys中列出的列的数据:
keys = [ 'year' , 'month' , 'day' , 'game_date' , 'team1' , 'team1_score' , 'team2' , 'team2_score' , 'game_status' ]现在,让我们导入带有pyquery和re的代码。Regex 将用于分隔从页面来源获取的日期:
from pyquery import PyQuery as pq
import re
sourceUrl = 'http://www.flyershistory.com/cgi-bin/ml-poffs.cgi'
dataSet = list ()
keys = [ 'year' , 'month' , 'day' , 'game_date' , 'team1' , 'team1_score' , 'team2' , 'team2_score' , 'game_status' ]
def read_url(url):
"""Read given Url , Returns pyquery object for page content"""
pageSource = pq(url)
return pq(pageSource)
if __name__ == '__main__' :
page = read_url(sourceUrl) 这里,read_url()接受一个参数,即指向页面的链接,并返回页面源或pageSource的 PyQuery 对象。PyQuery 自动返回所提供 URL 的页面源。也可以使用其他库获取页面源,如urllib、urllib3、requests和 LXML,并传递来创建 PyQuery 对象:
tableRows = page.find( "h1:contains('AHL Playoff Results') + table tr" )
print ( " \n Total rows found :" , tableRows. __len__ ())tableRows是一个 PyQuery 对象,用于遍历<table>中的<tr>,该<tr>位于<h1>之后。包含AHL Playoff Results文本,通过find()函数获取。正如我们在下面的输出中所看到的,总共存在463``<tr>元素,但从可用<td>的数量和实际数据来看,实际获得的记录数量可能更低:
Total rows found : 463让我们再做一些处理。每个<tr>或tr元素都是tableRows的一项,通过items()方法遍历,使用其索引并检索其包含的数据,找到准确的<td>或td:
for tr in tableRows.items():
#few <tr> contains single <td> and is omitted using the condition
team1 = tr.find( 'td' ).eq( 1 ).text()
i f team1 != '' :
game_date = tr.find( 'td' ).eq( 0 ).text()
dates = re.search( r'(.*)-(.*)-(.*)' ,game_date)
team1_score = tr.find( 'td' ).eq( 2 ).text()
team2 = tr.find( 'td' ).eq( 4 ).text()
team2_score = tr.find( 'td' ).eq( 5 ).text()
#check Game Status should be either 'W' or 'L'
game_status = tr.find( 'td' ).eq( 6 ).text()
if not re.match( r'[WL]' ,game_status):
game_status = tr.find( 'td' ).eq( 7 ).text()
#breaking down date in year,month and day
year = dates.group( 3 )
month = dates.group( 2 )
day = dates.group( 1 )
#preparing exact year value
if len (year)== 2 and int (year)>= 68 :
year = '19' +year
elif len (year)== 2 and int (year) < 68 :
year = '20' +year
else :
pass 到目前为止,已经从目标<td>收集了所需的数据,并且在year的情况下进行了格式化。Regex 也已应用于代码中,并与dates和game_status一起使用。最后,收集的对象作为列表附加到dataSet:
#appending individual data list to the dataSet
dataSet.append([year,month,day,game_date,team1,team1_score,team2,team2_score,game_status])
print ( " \n Total Game Status, found :" , len (dataSet))
print (dataSet)关于总记录计数和dataSet的输出如下:
Total Game Status, found : 341
[['1968', 'Apr', '3', '3-Apr-68', 'Buff', '2', 'Que', '4', 'W'],
['1968', 'Apr', '5', '5-Apr-68', 'Buff', '1', 'Que', '3', 'W'],
['1968', 'Apr', '9', '9-Apr-68', 'Que', '7', 'Buff', '10', 'L'],
['1968', 'Apr', '10', '10-Apr-68', 'Que', '4', 'Buff', '7', 'L'],
['1968', 'Apr', '12', '12-Apr-68', 'Buff', '1', 'Que', '3', 'W'],
.................
['2008', 'May', '9', '9-May-2008', 'Phantoms', '3', 'Wilkes-Barre', '1', 'L'],
['2009', 'Apr', '16', '16-Apr-09', 'Phantoms', '2', 'Hershey', '4', 'L'],
['2009', 'Apr', '18', '18-Apr-09', 'Phantoms', '2', 'Hershey', '6', 'L'],
['2009', 'Apr', '22', '22-Apr-09', 'Hershey', '2', 'Phantoms', '3', 'L'],
['2009', 'Apr', '24', '24-Apr-09', 'Hershey', '0', 'Phantoms', '1', 'L']]在本例中,我们将从中提取sitemap.xml文件中为博客找到的 URLhttps://webscraping.com/sitemap.xml 。
在前面的示例中,我们使用了 HTML 内容,但 PyQuery 也可以用于遍历 XML 文件内容。默认情况下,pyquery使用基于 LXML 的xml解析器,可以在创建 PyQuery 对象时提供该解析器。我们将在文件内容中同时使用lxml.html和xml。
For more information on pyquery and parser, please visit the Exploring pyquery section of this chapter. For information regarding the site map, please visit Chapter 1, Web Scraping Fundamentals, the Data finding techniques (seeking data from the web) section, in the Sitemaps subsection.
以下屏幕截图显示了sitemap.xml文件中可用的内容:
sitemap.xml file from https://webscraping.com
首先,我们导入pyquery并读取文件内容为xmlFile
from pyquery import PyQuery as pq
if __name__ == '__main__' :
# reading file
xmlFile = open ( 'sitemap.xml' , 'r' ).read() 在这里,我们将使用lxml.html解析器解析xmlFile,将参数解析器parser='html'传递给 PyQuery:
# creating PyQuery object using parser 'html'
urlHTML = pq(xmlFile, parser = 'html' )
print ( "Children Length: " ,urlHTML.children(). __len__ ())
print ( "First Children: " ,urlHTML.children().eq( 0 ))
print ( "Inner Child/First Children: " ,urlHTML.children().children().eq( 0 ))使用 PyQuery 的urlHTML对象,我们可以检查从数据中获得的计数和子元素,如以下输出所示:
Children Length: 137
First Children:
<url>
<loc>https://webscraping.com</loc>
</url>
Inner Child/First Children: <loc>https://webscraping.com</loc>如我们所见,urlHTML.children()包含查找 URL 所需的元素。我们可以使用items()方法处理这些数据,该方法遍历获得的每个元素。让我们创建dataSet(Pythonlist()),它将附加提取的 URL。
通过使用包含blog字符串的选择器,可以使用urlHTML.children().find( 'loc:contains("blog")' ).items()执行基于元素的迭代:
dataSet= list ()
for url in urlHTML.children().find( 'loc:contains("blog")' ).items():
dataSet.append(url.text())
print ( "Length of dataSet: " , len (dataSet))
print (dataSet)最后,我们将收到以下输出:
Length of dataSet: 131
['https://webscraping.com/blog', 'https://webscraping.com/blog/10/', 'https://webscraping.com/blog/11/', 'https://webscraping.com/blog/12/', 'https://webscraping.com/blog/13/', 'https://webscraping.com/blog/2/'
,.................................................................................,
'https://webscraping.com/blog/Reverse-Geocode/', 'https://webscraping.com/blog/Scraping-Flash-based-websites/', 'https://webscraping.com/blog/Scraping-JavaScript-based-web-pages-with-Chickenfoot/', 'https://webscraping.com/blog/category/web2py', 'https://webscraping.com/blog/category/webkit', 'https://webscraping.com/blog/category/website/', 'https://webscraping.com/blog/category/xpath']在本例中,我们将使用 PyQueryurlXML对象处理 XML 内容,该对象使用parser='xml':
#creating PyQuery object using parser 'xml'
urlXML = pq(xmlFile, parser='xml')
print ("Children Length: ",urlXML.children(). __len__ ())前面的代码返回子级计数的长度,即137URL 总数:
Children Length: 137 如以下代码所示,第一个和内部子元素返回我们愿意提取的所需 URL 内容:
print ( "First Children: " , urlXML.children().eq( 0 ))
print ( "Inner Child/First Children: " , urlXML.children().children().eq( 0 ))
First Children:
<url >
<loc>https://webscraping.com</loc>
</url>
Inner Child/First Children:
<loc >https://webscraping.com</loc>让我们使用类似于案例 1 中使用的选择器(使用 HTML 解析器部分)来处理子元素:
dataSet= list ()
for url in urlXML.children().find( 'loc:contains("blog")' ).items():
dataSet.append(url.text())
print ( "Length of dataSet: " , len (dataSet))
print (dataSet)在这里,我们在dataSet中没有收到任何输出,而且选择器似乎不像在案例 1 中那样工作——使用 HTML 解析器:
Length of dataSet: 0
[]让我们使用以下代码验证此情况:
for url in urlXML.children().children().items():
print (url)
break
<loc >https://webscraping.com</loc>我们收到的节点属于https://www.sitemaps.org/schemas/sitemap/0.9 。如果不删除名称空间选择器,它将无法工作。
remove_namespace()函数可用于 PyQuery 对象,并对其进行最终输出处理,如下代码所示:
for url in urlXML.remove_namespaces().children().find( 'loc:contains("blog")' ).items():
dataSet.append(url.text())
print ( "Length of dataSet: " , len (dataSet))
print (dataSet)我们收到以下输出:
Length of dataSet: 131
['https://webscraping.com/blog', 'https://webscraping.com/blog/10/', 'https://webscraping.com/blog/11/', 'https://webscraping.com/blog/12/', 'https://webscraping.com/blog/13/', 'https://webscraping.com/blog/2/', 'https://webscraping.com/blog/3/', 'https://webscraping.com/blog/4/', 'https://webscraping.com/blog/5/', 'https://webscraping.com/blog/6/', 'https://webscraping.com/blog/7/', 'https://webscraping.com/blog/8/',
.................................................................
'https://webscraping.com/blog/category/screenshot', 'https://webscraping.com/blog/category/sitescraper', 'https://webscraping.com/blog/category/sqlite', 'https://webscraping.com/blog/category/user-agent', 'https://webscraping.com/blog/category/web2py', 'https://webscraping.com/blog/category/webkit', 'https://webscraping.com/blog/category/website/', 'https://webscraping.com/blog/category/xpath']The PyQuery remove_namespace() and xhtml_to_html() methods remove the namespaces from XML and XHTML, respectively. Use of these two methods allows us to work with elements that use HTML-related properties.
我们也可以用不同的方法处理相同的内容;也就是说,通过使用正则表达式并根据需要获取输出。让我们继续执行以下代码:
print ("URLs using Children: ",urlXML.children().text())
#print ("URLs using Children: ",urlXML.children().children().text())
#print ("URLs using Children: ",urlXML.text())PyQuerychildren()object 方法返回所有子节点,text()提取文本内容,如下图:
URLs using Children: https://webscraping.com https://webscraping.com/about
https://webscraping.com/blog .............https://webscraping.com/blog/Converting-UK-Easting-Northing-coordinates/ https://webscraping.com/blog/Crawling-with-threads/ https://webscraping.com/blog/Discount-coupons-for-data-store/ https://webscraping.com/blog/Extracting-article-summaries/ https://webscraping.com/blog/10/ https://webscraping.com/feedback..........如前面的输出所示,来自子节点的所有链接都作为单个字符串返回:
blogXML = re.split( r'\s' ,urlXML .children().text())
print ("Length of blogXML: ", len (blogXML))
#filter(), filters URLs from blogXML that matches string 'blog'
dataSet= list ( filter ( lambda blogXML:re.findall( r'blog' ,blogXML),blogXML))
print ("Length of dataSet: ", len (dataSet))
print ("Blog Urls: ",dataSet)这里,re.split()用于拆分接收到的 URL 字符串,其中包含空格字符\s。这将返回总共139个元素。最后,使用re.findall()过滤blogXML,在blogXML元素中找到blog字符串,结果如下:
Length of blogXML: 139
Length of dataSet: 131
Blog Urls: ['https://webscraping.com/blog', 'https://webscraping.com/blog/10/', 'https://webscraping.com/blog/11/', 'https://webscraping.com/blog/12/', 'https://webscraping.com/blog/13/', 'https://webscraping.com/blog/2/', 'https://webscraping.com/blog/3/', 'https://webscraping.com/blog/4/', 'https://webscraping.com/blog/5/', 'https://webscraping.com/blog/6/', 'https://webscraping.com/blog/7/', 'https://webscraping.com/blog/8/',...............................................
'https://webscraping.com/blog/category/web2py', 'https://webscraping.com/blog/category/webkit', 'https://webscraping.com/blog/category/website/', 'https://webscraping.com/blog/category/xpath']在本节中,我们使用了一些刮取技术从文件和网站中提取所需的内容。内容识别和刮取的要求是相当动态的,而且也是基于网站的结构。有了pyquery这样的库,我们可以获得并部署必要的工具和技术,以高效的方式进行刮取。
pyquery似乎在处理 CSS 选择器方面更有效,并提供了许多与 LXML 相关的功能。简单易读的代码总是有需求的,pyquery提供了这些特性,用于抓取。在本章中,我们探讨了执行刮片任务时可能遇到的各种情况,并成功地获得了预期的结果。
在下一章中,我们将探索更多与 web 抓取相关的库。
- PyQuery 完整 API:https://pyquery.readthedocs.io/en/latest/api.html
- pyquery:Python 的类似 jquery 的库:https://pythonhosted.org/pyquery/
- CSS 选择器参考:https://www.w3schools.com/cssref/css_selectors.asp
- CSS 伪类和元素:https://www.w3schools.com/css/css_pseudo_elements.asp
- CSS 信息:http://www.css3.info/ 和https://developer.mozilla.org/en-US/docs/Web/CSS
- 网站地图:https://www.sitemaps.org/
- XML*:https://www.w3schools.com/xml/ 和https://www.w3.org/XML/






