0%

Scrapy

Scrapy框架的学习

1. Scrapy的工作原理

1580195020653

2. 实现一个简单的爬虫框架所需要进行的步骤

1. 创建一个start.py文件

这个纯粹是为了不想每次启动爬虫都重新输入一下命令(毕竟也不是记得所有爬虫的名字)

1
2
3
4
5
# 导入cmdline用于执行cmd命令的
from scrapy import cmdline
# scrapy crawl 爬虫名字 是启动爬虫所需要输入的cmd命令
# cmd.execute是需要获取到一个类似于列表的东西,因此需要把所有的命令都拆开,故使用了一个split
cmdline.execute("scrapy crawl lianjia_spider".split())

2. 修改settings.py

2.1 ROBOTSTXT_OBEY = False

默认选项是True,即遵守robots.txt的规则。而这个robots.txt是遵循Robot协议的一个文件,它保存在网站的服务器中,作用是:告诉搜索引擎,本网站哪些目录下的网页 不希望 你进行爬取收录。在Scrapy启动后,会在第一时间访问网站的robots.txt文件,然后决定该网站的爬取范围。

当然,我们并不是在做搜索引擎,而且在某些情况下我们想要获取的内容恰恰是被robots.txt所禁止访问的。所以,有些时候,我们就要将此配置项设置为False,拒绝遵守robot协议。

2.2 启用DEFAULT_REQUEST_HEADERS

因为现在的大部分网站都进行了反爬虫措施,比如知乎,豆瓣等等。作为一个spider就要学会反反爬虫。最简单实用的措施就是设置headers。

settings.py里面开启DEFAULT_REQUEST_HEADERS,根据需要设置相关内容即可,主要就是配置User-Agent

1
2
3
4
5
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36",
}
2.3 如果想要将爬取的数据记录在文件(json格式等)中,需要开启ITEM_PIPELINES
1
2
3
4
ITEM_PIPELINES = {
'House.pipelines.HousePipeline': 300,
# 'House.pipelines.HouseCsvPipeline': 300,
}

Ps:后面的数字表示优先级,数字越小表示优先级越高

3. 完善items.py

默认有一个Item类,里面需要填写你要获取的内容,如下:

1
2
3
4
5
6
7
8
9
10
class HouseItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
# pass
name = scrapy.Field()
district = scrapy.Field()
location_2 = scrapy.Field()
location_3 = scrapy.Field()
price = scrapy.Field()
area = scrapy.Field()

Field对象用于为每个字段指定元数据。我们可以为每个字段指定任何种类的元数据。对Field对象接受的值没有限制。处于同样原因,没有所有可用元数据键的参考列表。Field对象中定义的每个键可以由不同的组件使用,并且只有那些组件知道它。Field对象的主要目标是提供一种在一个地方定义所有字段元数据的方法。通常,那些行为取决于每个字段的组件使用某些字段键来配置该行为、

4. 创建spider.py

运行cmd命令

scrapy genspider xxx(爬虫名字) “XXX(网址域名)”

里面默认应该有name(爬虫名字), allowed_domains(允许爬取的网站的域名), start_urls(首先爬取的网页的url,注意这个是一个列表,意味着可以有很多网址)

还有一个parse(self, response)方法:这个是用来具体在爬取到一个页面,然后对这个页面进行解析的一个方法,最后需要返回爬取的东西(即上一步定义过的一些属性)

在这个parse方法中,我们需要定义爬取该网页的什么内容。首先需要了解网页的组成。然后通过浏览器带有的右键->检查来查看网页元素,然后获取我们所需要的内容在网页的什么地方。可以学习xPath的表示方法,不过直接找到该元素,然后右键->Copy->Copy xPath即可轻松获取到xpath

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def parse(self, response):
print("=" * 40)
# 创建一个item实例
item = HouseItem()
# 由于我们获取的东西在一个大的li标签下面,因此我们可以用循环来解决这个问题
path = '/html/body/div[4]/ul[2]/li'
for each in response.xpath(path):
# 获取a标签下的内容,用get函数即可
item["name"] = each.xpath('./div/div[1]/a/text()').get()
# 如果我们需要获取到span标签的所有内容,可以采用getall()函数,然后返回值即为我们想要获取的多个内容
priceNumber, priceUnit = each.xpath('./div/div[6]/div[1]/span/text()').getall()
print(priceNumber, priceUnit)
item["price"] = priceNumber + priceUnit[1:]
item["area"] = each.xpath('./div/div[3]/span/text()').get()
item["district"] = each.xpath('./div/div[2]/span[1]/text()').get()
item["location_2"] = each.xpath('./div/div[2]/span[2]/text()').get()
item["location_3"] = each.xpath('./div/div[2]/a/text()').get()
# 判断我们获取的数据是否为有效数据
if item["name"] and item["price"] and item["area"]:
# 如果获取的数据为有效数据,则需要将该item返回
yield item

5. 修改pipelines.py

该文件中默认有process_item(self, item, spider)方法,该方法主要是将获取到的item写入文件。在此之前,我们需要定义open_spider(self, spider)和close_spider(self, spider)方法

当然,也可以先写一个__init__(self)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class HousePipeline(object):
def __init__(self):
# 将打开文件的操作写在open_spider方法中或者__init__方法中都可

def open_spider(self, spider):
self.fp = open("data.json", "w", encoding='utf-8')

def process_item(self, item, spider):
# 先将item变换成字典
dict_item = dict(item)
# 变成json格式, 注意这边一定要设置ensure_ascii=False,不然会出现一堆看不懂的东西
json_str = json.dumps(dict_item, ensure_ascii=False) + '\n'
# 写入文件
self.fp.write(json_str)
# 返回
return item

def close_spider(self, spider):
self.fp.close()

基本上就大功告成了。最后只需要python start.py即可。

3. CrawlSpider

创建爬虫文件:

scrapy genspider -t crawl xxx(爬虫名字) “XXXX(网站域名)”

需要使用LinkWxtractorRule。这两个东西决定爬虫的具体走向。

  • allow设置规则的方法:要能够限制在我们想要的url上面,不要跟其他的url产生相同的正则表达式即可
  • 什么情况下使用follow:如果在爬取页面的时候,需要将满足当前条件的url再进行跟进,那么就设置为True,否则设置为False
  • 什么情况下指定callback:如果这个url对应的页面,只是为了获取更多的url,并不需要里面的数据,那么就可以不指定callback,如果想要获取url对应页面中的数据,那么就需要指定一个callback来解析数据

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class WxappSpiderSpider(CrawlSpider):
name = 'wxapp_spider'
allowed_domains = ['wxapp-union.com']
start_urls = ['http://www.wxapp-union.com/portal.php?mod=list&catid=2&page=1']

# callback,如果需要对于爬到的页面进行解析,就需要callback,如果只需要爬取网页链接,则不需要callback函数,不过需要考虑是否设置follow,跟进下一页
rules = (
# 因为这个页面是主页面,有很多具体文章的链接,对于这种页面我们通常只需要提取出里面具体文章的链接即可,因此我们不需要callback函数,同时,在页面下面有很多第xx页,因此针对这种情况,我们需要follow跟进继续爬取页面
Rule(LinkExtractor(allow=r'.+mod=list&catid=2&page=\d'), follow=True),
# 这个页面是具体文章的页面,我们需要具体爬取文章的名称,作者,时间,内容等信息,因此我们需要指定callback来具体解析该网页,并且我们不需要再在这个页面上继续点击进入别的页面,里面的网址的信息我们不需要,设置follow=False
Rule(LinkExtractor(allow=r'.+article-.+\.html'), callback="parse_item", follow=False)
)

def parse_item(self, response):
item = {}
print("=" * 40)
title = response.xpath("//*[@id='ct']/div[1]/div/div[1]/div/div[2]/div[1]/h1/text()").get()
author_p = response.xpath("//*[@id='ct']/div[1]/div/div[1]/div/div[2]/div[3]/div[1]/p")
author = author_p.xpath(".//a/text()").get()
pub_time = author_p.xpath(".//span/text()").get()
print(title)
print("author:%s/pub_time:%s" % (author, pub_time))
print("=" * 40)
item = WxappItem(title=title, author=author, pub_time=pub_time)
yield item

Ps: 还有一个稍微不同的地方,因为这个pipelines的方式有很多,这边我们采取了exporter的方式,写法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
from scrapy.exporters import JsonLinesItemExporter

class WxappPipeline(object):
def __init__(self):
self.fp = open('wechat_data.json', 'wb')
self.exporter = JsonLinesItemExporter(self.fp, ensure_ascii=False, encoding='utf-8')

def process_item(self, item, spider):
self.exporter.export_item(item)
return item

def close_spider(self, spider):
self.fp.close()

Scrapy Shell

我们想要在爬虫中使用xpath, beautifulsoup, 正则表达式, css选择器等来提取想要的数据。但是因为Scrapy是一个比较重的框架。每次运行起来都需要等待一段时间。因此要去验证我们写的提取规则是否正确,是一个比较麻烦的事情。因此Scrapy提供了一个shell,用来方便的测试规则。当然也不局限于这一个功能。

如果想要执行Scrapy命令,需要进入到Scrapy所在的环境中

如果想要读取某个项目的配置信息,那么先应该进入到这个项目中,再执行下面的命令:

scrapy shell XXX(网站地址)

然后,就像在写代码一样操作即可

1580200971133

Request和Response对象

GET & POST

在客户机和服务器之间进行请求-响应时,两种最常被用到的方法是:GETPOST

GET:从指定的资源请求数据

POST:向指定的资源提交要被处理的数据

GET POST
后退按钮/刷新 无害 数据会被重新提交(浏览器应该告知用户数据会被重新提交)
缓存 能被缓存 不能缓存
对数据长度的限制 是的。当发送数据时,GET方法向URL添加数据;URL的长度是被限制的(URL的最大长度是2048个字符) 无限制
对数据类型的限制 只允许ASCII字符 没有限制,也允许二进制字符
安全性 与POST相比,GET的安全性较差,因为发送的数据是URL的一部分。(在发送密码或其他敏感信息的时候绝对不要使用GET!) POST比GET更安全,因为参数不会被保存在浏览器历史或web服务器日志中
可见性 数据在URL中对所有人是可见的 数据不会显示在URL中

Request对象

Request对象在我们写爬虫时,爬去一页的数据需要重新发送一个请求的时候调用。这个类需要传递一些参数,较常用的有以下:

  • url:这个request对象发送请求的url

  • callback:在下载器下载完相应的数据后执行的回调函数

  • method:请求的方法,默认为GET方法,可以设置为其他方法(如POST)

    如果我们想要在请求数据的时候发送post对象,那么这时候需要使用Request的子类FormRequest来实现。如果想要在爬虫一开始的时候我们就发送POST请求,那么需要在爬虫类中重写start_requests(self)方法,并且不再调用start_urls里的url。(如果不重写的话,就回默认从startUrls中读取url,并且是发送GET请求)

  • headers:请求头,对于一些固定的设置,放在settings.py中指定就可以了。对于那些非固定的,可以在发送请求的时候指定

  • meta:比较常用。用于在不同的请求之间传递数据

    ​ 比如,我们在浏览概览页和浏览详情页的时候,有些东西我们在概览页就可以得知,例如文章名字,作者,发布时间等等,那么我们在详情页中就可以只获取文章的内容,那么我们有些数据就需要共享,可以写在meta中。

  • encoding:编码,默认为utf-8

  • dont_filter:表示不由调度器过滤,在执行多次重复的请求的时候用的比较多

    不发送重复的请求~~

  • errback:在发生错误的时候执行

Response对象

Response对象一般是由Scrapy给你自动构建的。因此开发者不需要关心如何创建Response对象,而是如何使用。Response对象有很多属性,可以用来提取数据的。主要有以下属性:

  • meta:从其他请求传过来的meta属性。可以用来保持多个请求之间的数据连接

  • encoding:返回当前字符串编码和解码的格式

  • text:将返回来的数据作为unicode字符串返回

    其实textbody保存的都是网页源代码,但是body是没有解码过的,而text是解码成unicode字符的,如果想要解码成别的字符,则可以通过body自己解码

  • body:将返回来的数据作为byte字符串返回(在网络和硬盘之间通信的时候,其实用的是byte类型)

  • xpathxpath选择器

  • csscss选择器

下载文件和图片

Scrapy为下载item中包含的文件(比如在爬取到产品时,同时也想保存对应的图片)提供了一个可重用的item pipelines。这些pipelines有些共同的方法和结构,我们称之为media pipeline。一般来说你会使用File pipelines或者Image Pipeline

为什么要使用scrapy内置的下载方法?

  • 可避免重新下载最近已经下载过的数据
  • 可以方便的指定文件存储的路径
  • 可以将下载的图片转换成通用的格式,比如jpg, png等
  • 可以方便的生成缩略图
  • 可以方便的检测图片的宽和高,确保满足最小限制
  • 异步下载,效率非常高

下载文件的Files Pipeline

当使用Files Pipeline下载文件的时候,按照以下步骤来完成:

  • 定义好一个Item,然后在这个item中定义两个属性,分别为file_urls以及filesfile_urls是用来存储需要下载的文件的url链接,需要给一个列表。
  • 当文件下载完成后,会把文件下载的相关信息存储到itemfiles属性中。比如下载路径,下载的url和文件的校验码等
  • 在配置文件的settings.py中设置FILE_STORE,这个配置是用来设置文件下载下来的路径
  • 启动pipeline:在ITEM_PIPELINES中设置scrapy.pipelines.files.FilesPipeline:1

下载图片的Images Pipeline

当使用Images Pipeline下载文件的时候,按照以下步骤来完成:

  • 定义好一个Item,然后在这个item中定义两个属性,分别为image_urls以及imagesImage_urls是用来存储需要下载的图片的url链接,需要给一个列表

  • 当文件下载完成后,会把文件下载的相关信息存储到itemimages属性中。比如下载路径、下载的url和图片的校验码等等呢个

  • 在配置文件的settings.py中设置IMAGES_STORE,这个配置是用来设置图片下载下来的路径

  • 启动pipeline:在ITEM_PIPELINES中设置scrapy.pipelines, images,scrapy.pipelines.images.ImagesPipeline:1