说明:
今天主要学习一下翻页的功能,手动翻页的效果,前面的基础操作这里不不再依次讲解截图说明了,如果不太懂,可以参考我的上一篇scrapy博客:
https://blog.csdn.net/weixin_42081389/article/details/102390279
我的配置:
1 | windows10系统 |
2 | python3.6 |
目标网站:
http://lab.scrapyd.cn/
因为这个网站是get,响应的页面就能找到下一页的url,我记得之前测试翻页是用的腾讯招聘网站,但是现在腾讯招聘的页面改成异步获取的json数据了,如果真的爬取,我觉得直接使用requests模块比较方便,如果用scrapy,可以直接把开始的start_urls列表换成一个异步url列表集,我感觉使用scrapy那样爬取多此一举了。
比如这样,url列表集:
1 | start_urls = ['https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1570587185160&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex={}&pageSize=10&language=zh-cn&area=cn'.format(i) for i in range(1,101)] |
不过这样的我不写,这样的和我的第一个博客没有什么区别,只不过初始化的url列表数量多了而已。
今天爬虫(手动提取url,发送get请求)
1、创建项目+初始化爬虫文件:
1 | scrapy startpoject scrapyd_cn |
2 | cd scrapyd_cn |
3 | scapy genspider scrapyd lab.scrapyd.cn/ |
生成文件:
2、在setting中配置
1 | 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 | ROBOTSTXT_OBEY = False |
3 | |
4 | _LEVEL = 'DEBUG' |
5 | LOG_LEVEL = "WARNING" |
6 | |
7 | ITEM_PIPELINES = { |
8 | 'scrapyd_cn.pipelines.ScrapydCnPipeline': 300, |
9 | } |
3、修改items.py:
这里我们只要三维数据,需要三个字段即可。
1 | import scrapy |
2 | |
3 | |
4 | class ScrapydCnItem(scrapy.Item): |
5 | # define the fields for your item here like: |
6 | text = scrapy.Field() |
7 | author = scrapy.Field() |
8 | url = scrapy.Field() |
9 | # pass |
4、修改爬虫程序:spiders/scrapyd.py
①、scrapy.Request()
这个只是和之前比这多了一个翻页功能,这个scrapy.Request()
里面有俩个必须要传递的参数,一个是url,一个是返回的函数,这里的parse是本身的方法中,继续处理数据直至页面结束,可以自己写多个方法,根据项目和网站需要定义方法,和需要的返回方法中。
1 | # 翻页 |
2 | yield scrapy.Request(url=next_page, callback=self.parse) |
其中还有几个常用的参数:
scrapy.Request()中几个常用参数解释:
1 | url:必填: |
2 | callback:必填:请求之后返回到方法中处理然后数据 |
3 | enthod:默认是get,如果是post需要自己手动更改,我这里是get,忽略了。 |
4 | cookies:字典类型,有些网站需要cookies,可以携带上单个的cookies。 |
5 | headers:字典类型,请求头,我的这个在setting里面加入了user-agent,这里不加也可以。 |
6 | meta:这个很常用,这里是方法之间传递参数的 |
7 | dont_filter:是否开启过滤,默认关闭,开启之后爬取过的url,下一次不会再爬取 |
8 | errback:和callback类似,但是是处理对应的请求url报错时会进入errback,可以进入将报错的url打印出来或者单独保留下来,后续手动测试查找报错原因 |
②、直接上我的代码:
1 | # -*- coding: utf-8 -*- |
2 | import scrapy |
3 | from scrapyd_cn.items import ScrapydCnItem |
4 | |
5 | class ScrapydSpider(scrapy.Spider): |
6 | name = 'scrapyd' |
7 | allowed_domains = ['lab.scrapyd.cn'] |
8 | start_urls = ['http://lab.scrapyd.cn//'] |
9 | |
10 | def parse(self, response): |
11 | # 1、提取每一页的数据 |
12 | div_list = response.xpath('//*[@id="main"]/div[@class="quote post"]') |
13 | for div in div_list: |
14 | # extract_first() 和 get() 返回的结果是一样的。 |
15 | text = div.xpath('./span[@class="text"]/text()').get() |
16 | # author = div.xpath('.//*[@class="author"]/text()').extract_first() |
17 | author = div.xpath('.//*[@class="author"]/text()').get() |
18 | url = div.xpath('.//a[contains(text(),"详情")]/@href').get() |
19 | # print("div", text, author, url) |
20 | item = ScrapydCnItem() |
21 | item['text'] = text |
22 | item['author'] = author |
23 | item['url'] = url |
24 | yield item |
25 | |
26 | # 2、下一页 |
27 | next_page = response.xpath('//*[@id="main"]//li[@class="next"]/a/@href').get() |
28 | print("下一页",next_page) |
29 | if next_page and len(next_page) > 5: |
30 | # 翻页 |
31 | yield scrapy.Request(url=next_page, callback=self.parse) |
32 | else: |
33 | print("没有下一页了"*10) |
5、管道处理(一般都在这里进行数据清洗和数据储存操作):pipelines.py
1、测试spider是什么:
下面是我测试的方法,之前使用过name属性进行过对不同的数据进行清洗和储存。你也可以自己研究测试。
1 | class ScrapydCnPipeline(object): |
2 | def process_item(self, item, spider): |
3 | # print("pipelines.py",item,spider) |
4 | # 1、测试spider是什么?,结果发现spdier就是我们爬虫程序,因为一个项目里面可以有多个爬虫程序, |
5 | # print("pipelines.py",dir(spider)) # 打印出spider含有的属性和方法 |
6 | # ['allowed_domains', 'close', 'crawler', 'custom_settings', 'from_crawler', 'handles_request', 'log', 'logger', 'make_requests_from_url', 'name', 'parse', 'set_crawler', 'settings', 'start_requests', 'start_urls', 'update_settings'] |
7 | # print("pipelines.py", spider.name) |
8 | # 发现我的爬虫程序的name是唯一的,如果一个项目有多个爬虫程序时,可以根据name进行分别进行建立不同的方法处理(比如不同的爬虫数据,需要存入不同的数据库中,或者需要的字段还不一致) |
9 | # pipelines.py scrapyd |
10 | |
11 | # 2、处理返回的数据 |
12 | |
13 | |
14 | return item |
2、保存到MongoDB数据库:
代码:
1 | from pymongo import MongoClient |
2 | |
3 | |
4 | class ScrapydCnPipeline(object): |
5 | |
6 | def open_spider(self, spider): |
7 | print("准备创建一个数据库") |
8 | # 这个会在项目开始时第一次进入pipelines.py进入,之后不再进入 |
9 | # 建立于MongoClient 的连接: |
10 | self.client = MongoClient('localhost', 27017) |
11 | # 得到数据库 |
12 | self.db = self.client['111_test_database_scrapyd_cn'] |
13 | # 得到一个集合 |
14 | self.collection = self.db['111_test_collection_scrapyd_cn'] |
15 | |
16 | def close_spider(self, spider): |
17 | print('项目结束,断开数据库连接') |
18 | # 这个会在结束时开始时第一次进入pipelines.py进入,之后不再进入 |
19 | self.client.close() |
20 | |
21 | |
22 | def process_item(self, item, spider): |
23 | # print("pipelines.py",item,spider) |
24 | # 1、测试spider是什么?,结果发现spdier就是我们爬虫程序,因为一个项目里面可以有多个爬虫程序, |
25 | # print("pipelines.py",dir(spider)) # 打印出spider含有的属性和方法 |
26 | # ['allowed_domains', 'close', 'crawler', 'custom_settings', 'from_crawler', 'handles_request', 'log', 'logger', 'make_requests_from_url', 'name', 'parse', 'set_crawler', 'settings', 'start_requests', 'start_urls', 'update_settings'] |
27 | # print("pipelines.py", spider.name) |
28 | # 发现我的爬虫程序的name是唯一的,如果一个项目有多个爬虫程序时,可以根据name进行分别进行建立不同的方法处理(比如不同的爬虫数据,需要存入不同的数据库中,或者需要的字段还不一致) |
29 | # pipelines.py scrapyd |
30 | |
31 | # 2、处理返回的数据 |
32 | # print("process_item", item, spider) |
33 | # print("type", type(item)) |
34 | # 储存到数据库 |
35 | print("准备保存到数据库",item) |
36 | self.collection.save(dict(item)) |
37 | |
38 | return item |
打印出的页面显示:
数据MongoDB存入成功:
6、我刚学scrapy对itmes模块的一个疑问:
不知道刚学scrapy时有没有和我一样,有这样一个疑问,爬虫程序中,我不继承items中的ScrapydCnItem类,直接用一个字典代替,其实,我的理解,如果不涉及过的爬虫数据类型保存,是一样的,但是如果涉及过多的类型数据保存,会影响数据的混乱保存。比如进入管道中的,一个程序中,我就有好几种数据分别保存到不同的数据库,这时,继承items中的dict数据进入管道pipelines.py就可以用
isinstance(item,ScrapydCnItem)
返回的是True和False,进行保存自己需要的数据类型,到对应的需求数据库中。
scrapy.Item进入源文件是继承一个dict类:
scrapy.Field() 进入源文件也是继承一个dict
爬虫文件中,我测试发现定义一个字典返回的管道数据中储存,结果和定义的items的类ScrapydCnItem最后进入管道的结果是一样的,但是类型不一样,最后我找到一个可以理解这定义这个items中ScrapydCnItem类dict的原因:
1、我改为item={}
爬虫spider/scrapyd.py中,改为item = {}:
pipelines.py中打印到处item类型,和print(“isinstance”,isinstance(item,ScrapydCnItem))
其中这个ScrapydCnItem类,是items.py中的目标数据字段的类。
from scrapyd_cn.items import ScrapydCnItem
上面的打印结果:
2、我改为:item = ScrapydCnItem()
爬虫spider/scrapyd.py中
pipelines.py中,用来打印出我的疑问
打印出管道中的结果:
3、对比,得出我认为的结论:
通过上面俩个打印出的结论,我也就自己给出了我的疑问答案,那就是,items的文件,在爬虫程序中继承过来的字段类型,看着是字典,但是可以根据这个进行不同的数据类型(根据继续的items中的类来体现),根据isinstance(item,ScrapydCnItem),ScrapydCnItem要改为你实际需求的数据类名,进行不同数据类型分开保存。
这也就是我自己对这个items中的定义目标数据字段的理解。