Scrapy开源Python网页抓取框架:创建不同网络爬虫教程
[id_1845539157]id_1624662778]

Scrapy 与 Requests、BeautifulSoup 等其他常用库存在主要区别,它能让您以优雅的方式解决常见的网络抓取问题 。
Scrapy的缺点在于学习曲线较为陡峭,存在许多需要学习的内容,不过这正是我们来到此处的目的 :)
在本教程中,我们会创建两个不一样的网络爬虫,一个是简单地从电子商务产品页面提取数据,另一个是更“复杂”地爬取整个电子商务目录!
基本概述
您能够运用pip来安装Scrapy ,然而需要留意,Scrapy文档大力推荐把它安装在专门的虚拟环境里,以此来防止和系统包产生冲突 。
我正在运用Virtualenv,我正在运用Virtualenvwrapper:
mkvirtualenv scrapy_env
和
pip install Scrapy
您现在可以使用以下命令创建一个新的 Scrapy 项目:
使用scrapy启动一个名为product_scraper的项目
这将为项目创建所有必要的样板文件。
├── product_scraper
│ ├── __init__.py
│ ├── __pycache__
│ ├── items.py
│ ├── middlewares.py
│ ├── pipelines.py
│ ├── settings.py
│ └── spiders
│ ├── __init__.py
│ └── __pycache__
└── scrapy.cfg
以下是这些文件和文件夹的简要概述:
抓取产品
在这个例子当中,我们会从一个虚拟的电子商务网站抓取产品。这是我们要抓取的首个产品:
我们将提取产品名称、图片、价格和描述。
Scrapy Shell
Scrapy有一个内置的shell,它能帮助您实时尝试和调试抓取代码,您能用它快速测试XPath表达式或CSS选择器,这是编写网络爬虫的很棒的工具,我一直在用它!
您能够把Scrapy Shell配置成使用另一个控制台,并非像IPython这样的默认Python控制台。您会得到自动完成功能,还有其他不错的好处,比如彩色输出。
为了能在您的scrapy Shell里使用它,您得把此行添加到您的scrapy.cfg文件中:
shell = ipython
配置完成后,您可以开始使用 scrapy shell:
$ scrapy shell --nolog
[s] Available Scrapy objects:
scrapy,scrapy模块(包含scrapy.Request、scrapy.Selector等)
[s] crawler
[s] item {}
[s] settings
[s] Useful shortcuts:
获取URL并更新本地对象(默认情况下,会跟随重定向),获取URL时可选择是否跟随重定向
获取一个scrapy.Request并更新本地对象
shelp(),这是Shell帮助功能,用于打印此帮助信息 。
在浏览器中查看响应,查看响应
In [1]:
我们可以通过简单地开始获取 URL:
获取,'https://clever-lichterman-044f16.netlify.com/products/taba-cream.1/'
这将从获取 /robot.txt 文件开始。
[scrapy核心引擎] 调试信息:已抓取 (404) (referer: None)
在这种情形下,不存在任何robots.txt,所以我们能看到404 HTTP代码。要是有robots.txt,默认状况下Scrapy会遵循规则。
您能够借助在settings.py里修改这个设置,从而将此行为禁用:
ROBOTSTXT_OBEY = True
那么你应该有这样的日志:
[scrapy核心引擎] 调试信息:已抓取 (200) (referer: None)
您现在能够查看您的响应对象,您现在能够查看您的响应标头,并且您可以尝试使用不同的XPath表达式来提取您想要的数据,您还可以尝试使用不同的CSS选择器来提取您想要的数据。
您可以直接在浏览器中查看响应:
view(response)
请注意,该页面在您的浏览器中呈现不佳,原因有许多种,这可能是CORS问题,可能是未执行的Javascript代码,也可能是无法在本地运行的资产的相对URL。
scrapy shell类似一个普通的Python shell,因此不要迟疑,在里面加载你最喜爱的脚本/函数。
提取数据
Scrapy 默认不会执行任何Javascript,所以要是您尝试抓取的网站使用了Angular、React.js等前端框架,那么您或许无法获取到所需的数据。
现在,让我们试着运用一些XPath表达式,来提取产品标题,还要提取价格:
我们要提取价格,会使用XPath表达式,我们会选择带有类的div,之后选择第一个css类“my-4”。
在[16]中,通过响应的xpath方法,选取class属性值为'my-4'的div元素下的span元素,并获取其文本内容 。
Out[16]: '20.00#39;
我还可以使用 CSS 选择器:
在[21]中:获取响应中类名为“my-4”的span元素内的文本内容 。
Out[21]: '20.00#39;
创建一个 Scrapy Spider
Spider是一个类,在这个类中您可以定义抓取行为,也就是确定需要抓取哪些链接或者URL,还可以定义提取行为,即明确要提取什么 。
以下是Spider用来抓取网站的不同步骤:
您可能想知道 parse 方法为何能返回诸多不同对象,这是出于灵活性的考量。假设您要抓取一个不存在任何站点地图的电子商务网站,您能够从抓取产品类别着手,如此一来这便会是首个解析方法。
然后,此方法会为每个产品类别生成一个Request对象,针对每个类别,需用新的回调方法parse2()处理分页,对于每个产品,要进行Item的实际抓取,以用于第三个解析函数。
使用Scrapy,您能够把抓取的数据当作简单的Python字典返回,不过最好运用内置的Scrapy Item类。它是我们抓取数据的一个简易容器,Scrapy会查看该项目的字段来进行诸多操作,比如将数据导出为不同格式(JSON / CSV...)、项目管道等 。
所以这是一个基本的产品类:
import scrapy
class Product(scrapy.Item):
product_url = scrapy.Field()
price = scrapy.Field()
title = scrapy.Field()
img_url = scrapy.Field()
现在我们可以使用命令行助手生成Spider:
使用scrapy工具生成一个名为myspider的爬虫,该爬虫针对的域名为mydomain.com 。
或者您能够手动完成,然后把您的Spider代码放置在 /spiders 目录里。
Scrapy 里存在不同类型的 Spider,这些 Spider 用于解决最常见的网页抓取用例。
# -*- coding: utf-8 -*-
import scrapy
从产品爬虫的项目中导入产品,将其命名为Product
定义一个名为EcomSpider的类,该类继承自scrapy.Spider
name = 'ecom_spider'
允许的域名包括,“clever-lichterman-044f16.netlify.com”
起始网址为,https://clever-lichterman-044f16.netlify.com/products/taba-cream.1/
def parse(self, response):
item = Product()
将“item['product_url']”赋值为“response.url”
将item字典中键为'price'的值,设置为通过XPath表达式("//div[@class='my-4']/span/text()")从response中获取到的文本内容 。
将变量item的'title'属性值,设置为通过XPath表达式从网页响应中提取到的值,该表达式为'//section[1]//h2/text()' 。
将item的['img_url']属性,赋值为通过response.xpath("//div[@class='product-slider']//img/@src").get(0)获取到的值 。
return item
在这个EcomSpider类中,有两个必需的属性:
当您使用CrawlSpider时,它可以跟踪不同域上的链接,allowed_domains是可选的但很重要。
然后,我刚刚用 XPath 表达式填充了 Product 字段,目的是提取我们之前看到的我想要的数据,之后我们返回该项目。
您能够依照如下方式去运行此代码,以此将结果导出为 JSON,(也能够将结果导出为 CSV)
运行scrapy,执行runspider命令,爬取ecom_spider.py文件,将结果输出到product.json文件中。
然后你应该得到一个不错的 JSON 文件:
[
{
“产品网址为”,“https://clever-lichterman-044f16.netlify.com/products/taba-cream.1/”
"price": "20.00#34;,
"title": "Taba Cream",
“img_url”的值为“https://clever-lichterman-044f16.netlify.com/images/products/product-2.png”
}
]
从 Web 提取数据时,您可能会遇到两个常见问题:
Scrapy有一个内置解决方案Item Loaders,这是一种填充我们Product对象的有趣方式。
您能够把多个XPath表达式添加到同一个Item字段,它会依照顺序对这些表达式进行测试。在默认情形下,如果找到了多个XPath,它会把它们全都加载到一个列表中。
您能够在Scrapy文档里寻得诸多输入处理器的示例,也能找到许多输出处理器的示例。
当您有转换数据的需求时,它很有用。当您有清理提取数据的需求时,它也很有用。比如,能从价格中提取货币。还能将一个单位转换为另一个单位,像把厘米单位转换为米,把摄氏度转换为华氏度......
在我们的网页里,我们能够找到产品标题,其具有不同的XPath表达式,分别是//title和//section[1]//h2/text()
在这种情况下,您可以使用以下方法和 Itemloader:
def parse(self, response):
创建一个ItemLoader对象,其item参数为Product(),response参数为response 。
l添加xpath,路径为price,具体内容是//div[@class='my-4']/span/text()
l添加xpath,其名称为title,路径为//section[1]//h2/text() 。
l添加xpath,其名称为title,路径为//title 。
l添加值,值为'product_url',其对应的值为response的url 。
return l.load_item()
通常您只需要第一个匹配的XPath,因此您需要添加output_processor=TakeFirst(),将它添加到您的项目的字段构造函数中。
在我们的例子里,我们仅想要每个字段的首个匹配的XPath,所以更好的办法是创建我们自己的Item Loader,并且声明一个默认的output_processor来获取首个匹配的XPath :
从scrapy.loader导入ItemLoader
从scrapy加载器处理器中导入TakeFirst,导入MapCompose,导入Join 。
定义一个名为remove_dollar_sign的函数,该函数接受一个参数value
返回值进行替换操作,将其中的 '#' 替换为空字符串 。
定义一个名为ProductLoader的类,它继承自ItemLoader 。
默认输出处理器,采用取第一个的方式 。
price_in进行了MapCompose操作,该操作使用了remove_dollar_sign函数。
我添加了一个输入处理器price_in,它用于从价格中删除美元符号。我正在使用MapCompose,它是一个内置处理器,需要一个或多个函数按顺序执行。惯例是在Item字段的名称中添加_in或_out来添加输入或输出处理器。