# -*- coding: utf-8 -*-import scrapy
classAliexpressTabletsSpider(scrapy.Spider):
name ='aliexpress_tablets'
allowed_domains =['aliexpress.com']
start_urls =['https://www.aliexpress.com/category/200216607/tablets.html','https://www.aliexpress.com/category/200216607/tablets/2.html?site=glo&g=y&tag=']defparse(self, response):print("procesing:"+response.url)#Extract data using css selectors
product_name=response.css('.product::text').extract()
price_range=response.css('.value::text').extract()#Extract data using xpath
orders=response.xpath("//em[@title='Total Orders']/text()").extract()
company_name=response.xpath("//a[@class='store $p4pLog']/text()").extract()
row_data=zip(product_name,price_range,orders,company_name)#Making extracted data row wisefor item in row_data:#create a dictionary to store the scraped info
scraped_info ={#key:value'page':response.url,'product_name': item[0],#item[0] means product in the list and so on, index tells what value to assign'price_range': item[1],'orders': item[2],'company_name': item[3],}#yield or give the scraped info to scrapyyield scraped_info
技术支持
信息:zip() 接受 n 个可迭代对象并返回一个由元组组成的列表。第 i 个元组的元素由各可迭代对象的第 i 个元素构成。 [8]
# -*- coding: utf-8 -*-import scrapy
classAliexpressTabletsSpider(scrapy.Spider):
name ='aliexpress_tablets'
allowed_domains =['aliexpress.com']
start_urls =['https://www.aliexpress.com/category/200216607/tablets.html','https://www.aliexpress.com/category/200216607/tablets/2.html?site=glo&g=y&tag=']
custom_settings={'FEED_URI':"aliexpress_%(time)s.json",'FEED_FORMAT':'json'}defparse(self, response):print("procesing:"+response.url)#Extract data using css selectors
product_name=response.css('.product::text').extract()
price_range=response.css('.value::text').extract()#Extract data using xpath
orders=response.xpath("//em[@title='Total Orders']/text()").extract()
company_name=response.xpath("//a[@class='store $p4pLog']/text()").extract()
row_data=zip(product_name,price_range,orders,company_name)#Making extracted data row wisefor item in row_data:#create a dictionary to store the scraped info
scraped_info ={#key:value'page':response.url,'product_name': item[0],#item[0] means product in the list and so on, index tells what value to assign'price_range': item[1],'orders': item[2],'company_name': item[3],}#yield or give the scraped info to Scrapyyield scraped_info
技术支持
import scrapy
classAliexpressTabletsSpider(scrapy.Spider):
name ='aliexpress_tablets'
allowed_domains =['aliexpress.com']
start_urls =['https://www.aliexpress.com/category/200216607/tablets.html']
custom_settings={'FEED_URI':"aliexpress_%(time)s.csv",'FEED_FORMAT':'csv'}defparse(self, response):print("procesing:"+response.url)#Extract data using css selectors
product_name=response.css('.product::text').extract()
price_range=response.css('.value::text').extract()#Extract data using xpath
orders=response.xpath("//em[@title='Total Orders']/text()").extract()
company_name=response.xpath("//a[@class='store $p4pLog']/text()").extract()
row_data=zip(product_name,price_range,orders,company_name)#Making extracted data row wisefor item in row_data:#create a dictionary to store the scraped info
scraped_info ={#key:value'page':response.url,'product_name': item[0],#item[0] means product in the list and so on, index tells what value to assign'price_range': item[1],'orders': item[2],'company_name': item[3],}#yield or give the scraped info to scrapyyield scraped_info
NEXT_PAGE_SELECTOR ='.ui-pagination-active + a::attr(href)'
next_page = response.css(NEXT_PAGE_SELECTOR).extract_first()if next_page:yield scrapy.Request(
response.urljoin(next_page),
callback=self.parse)技术支持
上述代码中:
您首先使用 next_page = response.css(NEXT_PAGE_SELECTOR).extract_first() 提取下一页的链接;若变量 next_page 得到非空链接,则进入 if 语句体。
如果您想先了解 Python 的网页抓取概览,建议学习 DataCamp 的 Web Scraping with Python 课程。
在本教程中,您将学习如何使用 Scrapy——一个能够处理海量数据的 Python 框架!我们将通过为电商网站 AliExpress.com 构建一个网页抓取器来学习 Scrapy。开始抓取吧!
具备基础的 HTML 和 CSS 知识将有助于您更轻松、更快速地理解本教程。想要快速回顾,可阅读这篇关于 HTML 和 CSS 的文章。
Scrapy 概览
网页抓取已成为从网络中提取信息以支持决策与分析的有效方式,也是数据科学工具箱中的重要组成部分。数据科学家应当掌握如何从网页收集数据,并将其以不同格式保存以供后续分析。
您在互联网上看到的任何网页都可以被爬取以获取信息,且网页上任何可见内容都可以被提取 [2]。每个网页都有其独特的结构与元素,因此您需要根据目标页面来编写相应的抓取器/蜘蛛。
Scrapy 使用
spiders(蜘蛛),它们是根据一组指令运行的独立爬虫 [1]。在 Scrapy 中,开发者可以复用代码,从而更容易构建与扩展大型爬取项目。Scrapy 与 BeautifulSoup 对比
本节将概述另一款广泛使用的网页抓取工具 BeautifulSoup,并与 Scrapy 进行比较。
Scrapy 是一个用于网页抓取的 Python 框架,为开发者提供了完整的一揽子方案,无需过多担心代码的维护。
Beautiful Soup 也被广泛用于网页抓取。它是一个用于解析 HTML 和 XML 文档并从中提取数据的 Python 包,支持 Python 2.6+ 和 Python 3。
简而言之,它们的差异如下:
requests、urlib2来打开 URL 并存储结果 [6]Twister进行异步(非阻塞)并发。multiprocessing,速度会比 Scrapy 慢。信息:同步表示必须等待当前任务完成后才能开始新任务;异步表示在前一个任务尚未完成前即可开始另一个任务。
这里有一篇有趣的 DataCamp BeautifulSoup 教程供您学习。
Scrapy 安装
在安装了 Python 3.0(及以上版本)后,如果您使用的是 Anaconda,可通过
conda安装scrapy。在 Anaconda Prompt 中输入以下命令:conda install -c conda-forge scrapy安装 Anaconda 的方法可参考这些 DataCamp 教程:Mac 与 Windows。
或者,您也可以使用 Python 包管理器
pip。适用于 Linux、Mac 和 Windows:pip install scrapyScrapy Shell
Scrapy 还提供了一个名为 Scrapy Shell 的抓取交互环境,开发者可以用它来测试对站点行为的假设。下面我们以电商网站 AliExpress 的平板电脑页面为例。您可以使用 Scrapy Shell 查看网页返回的组成部分,并据此满足您的需求。
打开命令行,输入以下命令:
scrapy shell如果您使用的是 Anaconda,也可以在 Anaconda Prompt 中输入上述命令。命令行或 Anaconda Prompt 中的输出将类似如下:
您需要在 Scrapy Shell 中使用
fetch命令对网页运行爬虫。爬虫(或蜘蛛)会遍历网页并下载文本与元数据。fetch(https://pt.aliexpress.com/category/201005406/special-store.html)注意:始终用引号包住 URL,单引号或双引号都可以
输出如下:
爬虫会返回一个response,您可以在 Shell 中使用
view(response)查看:view(response)随后网页会在默认浏览器中打开。
您可以在 Scrapy Shell 中使用以下命令查看原始 HTML 脚本:
print(response.text)您将看到用于生成网页的脚本。这与您在网页空白处右键单击并选择查看源代码或查看页面源代码时看到的内容相同。由于您只需要其中的关键信息,因此可借助浏览器开发者工具来检查所需元素。我们以以下元素为例:
右键单击您想要的元素,然后选择
inspect(检查),如下所示:浏览器的开发者工具对网页抓取非常有帮助。您可以看到该元素是一个带有
class为product 的<a>标签,其文本即为产品名称:使用 CSS 选择器进行提取
您可以通过元素属性或类等 CSS 选择器来提取。要提取产品名称,在 Scrapy Shell 中输入:
response.css(".product::text").extract_first()输出为:
extract_first()会提取与 CSS 选择器匹配的第一个元素。若要提取所有产品名称,使用extract():response.css(".product::text").extract()以下代码可提取产品的价格区间:
response.css(".value::text").extract()同理,您也可以尝试提取订单数量与店铺名称。
使用 XPath 进行提取
XPath 是一种用于在 XML 文档中选择节点的查询语言 [7]。您可以使用 XPath 在 XML 文档中导航。在底层,Scrapy 通过 Xpath 在 HTML 文档中定位元素。您在上文中使用的 CSS 选择器也会被转换为 XPath,但很多情况下 CSS 更易用。不过,您仍应了解 Scrapy 中 XPath 的工作方式。
回到 Scrapy Shell,像之前一样输入
fetch(https://pt.aliexpress.com/category/201005406/special-store.html/)。尝试以下代码片段 [3]:response.xpath('/html').extract()这将显示
<html>标签下的所有代码。/表示节点的直接子节点。如果您想获取html标签下的<div>标签,可写为 [3]:response.xpath('/html//div').extract()在 XPath 中,您必须理解
/与//的用法,以便在子节点与后代节点之间导航。这里有一篇关于 XPath 节点 的教程以及一些可尝试的示例。如果您想获取所有
<div>标签,也可以不使用/html,直接向下钻取 [3]:response.xpath("//div").extract()您还可以通过使用属性及其值来进一步过滤起始节点并定位到目标节点。下面是使用类及其值的语法:
response.xpath("//div[@class='quote']/span[@class='text']").extract()response.xpath("//div[@class='quote']/span[@class='text']/text()").extract()来看下面这段 HTML 代码:
您想获取
<a>标签内的文本,该标签是带有类site-notice-container container的<div>的子节点,可以这样做:response.xpath('//div[@class="site-notice-container container"]/a[@class="notice-close"]/text()').extract()创建 Scrapy 项目与自定义蜘蛛
网页抓取可用于构建聚合器,以便进行数据对比。例如,您想购买一台平板,希望对比产品与价格,就可以爬取目标页面并将数据存入 Excel 文件。这里我们将抓取 aliexpress.com 上的平板信息。
现在,您将为同一页面创建一个自定义蜘蛛。首先需要创建一个 Scrapy 项目,以存放代码与结果。在命令行或 Anaconda Prompt 中输入:
scrapy startproject aliexpress这将在您默认的 Python 或 Anaconda 安装目录下创建一个隐藏文件夹。
aliexpress将作为该文件夹名称(您也可自定义)。您可以通过资源管理器直接查看其内容。其结构如下:__init.py____init.py__创建项目后,切换到新建目录并输入以下命令:
[scrapy genspider aliexpress_tablets](https://pt.aliexpress.com/category/201005406/special-store.html)这将在上述的
spiders目录下创建名为aliexpress_tablets.py的模板文件。文件中的代码如下:在上述代码中,您可以看到
name、allowed_domains、sstart_urls和parse函数。scrapy crawl name_of_spider。response(与 Scrapy Shell 中相同)将传入该函数,在其中编写提取逻辑!信息:您可以在 Scrapy 蜘蛛的
parse()函数中使用 BeautifulSoup 来解析 HTML 文档。注意:您既可以像在 Scrapy Shell 部分那样,使用
response.css()通过CSS 选择器提取数据,也可以使用XPath(XML) 访问子元素。您将在pass()函数处编辑的代码中看到response.xpath()的示例。接下来我们修改
aliexpress_tablet.py文件。我在start_urls中又添加了一个 URL。您可以将提取逻辑添加到pass()函数处,如下所示:信息:
zip()接受 n 个可迭代对象并返回一个由元组组成的列表。第 i 个元组的元素由各可迭代对象的第 i 个元素构成。 [8]yield关键字用于定义生成器函数。生成器函数与普通函数类似,但用yield替代return。当调用方需要一个值时,包含yield的函数会将值“产出”给调用方,同时保留其本地状态,并在下一次继续从中断处执行。这里yield将生成的字典交给 Scrapy,之后由其处理与保存!现在可以运行蜘蛛:
scrapy crawl aliexpress_tablets您将在命令行看到很长的输出,如下所示:
导出数据
为了便于后续分析,您可能需要将数据导出为 CSV 或 JSON。本节将介绍如何将这些数据保存为 CSV 与 JSON 文件。
要保存为 CSV 文件,打开项目目录下的
settings.py并添加以下内容:保存
settings.py后,在项目目录中重新运行scrapy crawlaliexpress_tablets。CSV 文件如下所示:
注意:每次运行蜘蛛都会向文件追加内容。
Scrapy 的 Feed Export 还能在文件名中添加时间戳与蜘蛛名称,或用于确定存储目录。
%(time)s:在创建导出文件时会被时间戳替换 [5]%(name)s:会被蜘蛛名称替换 [5]例如:
ftp://user:[email protected]/scraping/feeds/%(name)s/%(time)s.json您在
settings.py中的 Feed 设置会应用到项目中的所有蜘蛛。也可以为某个特定蜘蛛设置自定义配置,以覆盖settings.py中的设置。response.url返回生成该响应的页面 URL。使用scrapy crawl aliexpress_tablets运行爬虫后,您可以查看生成的 json 文件:跟随链接
您可能已经注意到,在
start_urls中有两个链接。第二个链接是同一平板搜索结果的第 2 页。将所有链接都手动添加是不现实的。理想状态是:爬虫应能自行遍历所有页面,start_urls中只需提供起点。如果某个页面有后续页面,您会在页面底部看到用于前后翻页的导航器。在本教程场景中,您会看到如下所示:
其对应的代码如下:
如您所见,其中有一个带有类
.ui-pagination-active的<span>标签,表示当前页面;在其后方是所有指向下一页的<a>标签。每次都需要获取这个<span>标签之后的<a>标签。这里需要一点 CSS 知识!在这种情况下,您要获取的是兄弟节点而非子节点,因此需要编写一个 CSS 选择器,指示爬虫查找带有.ui-pagination-active类的<span>标签后面的<a>标签。按如下方式修改
aliexpress_tablets.py:上述代码中:
您首先使用
next_page = response.css(NEXT_PAGE_SELECTOR).extract_first()提取下一页的链接;若变量next_page得到非空链接,则进入if语句体。response.urljoin(next_page):parse()方法会用此方法构建新 URL,并创建一个新的请求,该请求稍后会发送至回调函数。 [9]获取新 URL 后,会再次抓取该链接,执行
for语句体并继续寻找下一页。该过程会一直持续,直到找不到下一页链接。现在您可以放松一下,欣赏蜘蛛自动抓取所有页面。上述蜘蛛将会从所有后续页面提取数据。这将是大量的抓取!但您的蜘蛛能胜任!下图可见文件大小已达到 1.1MB。
Scrapy 为您搞定一切!
本教程带您了解了 Scrapy、它与 BeautifulSoup 的比较、Scrapy Shell,以及如何在 Scrapy 中编写自己的蜘蛛。Scrapy 替您承担了大量繁琐的编码工作:从创建项目文件与文件夹到处理重复 URL,它让您在几分钟内完成高强度的网页抓取,并提供对常见数据格式的支持,方便您在其他程序中继续处理。此教程将帮助您理解 Scrapy 及其框架,以及它能为您做什么。要精通 Scrapy,您还需要深入学习它的各种强大功能,但本教程已让您能够高效地抓取成组网页。
欲进一步阅读,请参阅 Scrapy 官方文档。
此外,别忘了查看 DataCamp 的 Web Scraping with Python 课程。
参考资料