Scrapy 爬虫项目框架
1. Scrapy 简介
2. Scrapy 项目开发介绍
3. Scrapy 项目代码示例
3.3 spider目录下的sohu.py:编写提取数据的Spider
3.4 pipelines.py:将爬取后的item数据进行存储
1. Scrapy 简介
什么是 Scrapy?
Scrapy架构
- Scrapy Engine(引擎):Scrapy框架的核心部分,负责在Spider和ItemPipeline、Downloader、Scheduler之间通信、传递数据等。
- Spider(爬虫):发送需要爬取的链接给引擎,最后引擎把其他模块请求回来的数据再发送给爬虫,爬虫就去解析想要的数据。这个部分是我们开发者自己写的,因为要爬取哪些链接,页面中的哪些数据是我们需要的,都是由程序员自己决定。
- Scheduler(调度器):负责接收引擎发送过来的请求,并按照一定的方式进行排列和整理,负责调度请求的顺序等。
- Downloader(下载器):负责接收引擎传过来的下载请求,然后去网络上下载对应的数据再交还给引擎。
- Item Pipeline(管道):负责将Spider(爬虫)传递过来的数据进行保存。具体保存在哪里,应该看开发者自己的需求。
- Downloader Middlewares(下载中间件):可以扩展下载器和引擎之间通信功能的中间件。
- Spider Middlewares(Spider中间件):可以扩展引擎和爬虫之间通信功能的中间件。
Scrapy执行流程
Scrapy 框架的执行顺序:
- Spiders 的 yeild 将 request 发送给 Engine;
- Engine 对request 不做任何处理发送给 Scheduler;
- Scheduler 生成 request交给 Engine;
- Engine 拿到 request,通过 Middleware 进行层层过滤发送给 Downloader;
- Downloader 在网上获取到 response 数据之后,又经过 Middleware 进行层层过滤发送给 Engine;
- Engine 获取到 response 数据之后,返回给 Spiders,Spiders 的 parse() 方法对获取到的 response 数据进行处理,解析出 items 或者 requests;
- 将解析出来的 items 或者 requests 发送给 Engine;
- Engine 获取到 items 或者 requests,将 items 发送给Iitem Pipelines,将 requests 发送给Scheduler。
注意!只有当 Scheduler 中不存在任何 request 了,整个程序才会停止(也就是说,对于下载失败的 url,scrapy 也会重新下载)。
示例:
- 引擎:Hi!Spider, 你要处理哪一个网站?
- Spider:老大要我处理xxxx.com。
- 引擎:你把第一个需要处理的URL给我吧。
- Spider:给你,第一个URL是xxxxxxx.com。
- 引擎:Hi!调度器,我这有request请求你帮我排序入队一下。
- 调度器:好的,正在处理你等一下。
- 引擎:Hi!调度器,把你处理好的request请求给我。
- 调度器:给你,这是我处理好的request。
- 引擎:Hi!下载器,你按照老大的下载中间件的设置帮我下载一下这个request请求。
- 下载器:好的!给你,这是下载好的东西。(如果失败:sorry,这个request下载失败了。然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载)。
- 引擎:Hi!Spider,这是下载好的东西,并且已经按照老大的下载中间件处理过了,你自己处理一下(注意!这儿 responses 默认是交给 parse() 这个函数处理的)。
- Spider:(处理完毕数据之后对于需要跟进的URL)Hi!引擎,我这里有两个结果,这个是我需要跟进的URL,还有这个是我获取到的Item数据。
- 引擎:Hi!管道,我这儿有个item你帮我处理一下。Hi!调度器,这是需要跟进URL你帮我处理下。然后从第四步开始循环,直到获取完老大需要全部信息。
- 管道&调度器:好的,现在就做!
2. Scrapy 项目开发介绍
Scrapy 安装
- pip install scrapy
- pip install -i https://pypi.douban.com/simple/ scrapy
Scrapy 项目的开发步骤
可以使用命令行来新建一个爬虫工程,这个工程会自动按照scrapy的结构创建一个工程目录。
- 创建项目:scrapy startproject xxx(项目名字,不区分大小写)
- 明确目标 (编写items.py):明确你想要抓取的目标
- 制作爬虫 (spiders/xxspider.py):制作爬虫开始爬取网页
- 存储内容 (pipelines.py):设计管道存储爬取内容
- 启动程序的py文件(start.py):等同于此命令(scrapy crawl xxx -o xxx.json)
示例:创建项目
1)在e盘下新建一个目录:scrapy_crawler
2)在cmd进入目录:scrapy_crawler,然后执行scrapy startproject tutorial(给工程起名为tutorial),执行后会在scrapy_crawler创建好爬虫的工程,目录名为:tutorial
3)进入 tutorial 目录,执行命令:scrapy genspider sohu www.sohu.com(给爬虫文件起名,并设定种子URL)
以上三步执行完毕,则工程的框架建立完毕。后续需要你自己实现爬虫分析和保存的逻辑。目录结构如下图:
Scrapy 常用命令
Scrapy 保存信息的简单方法
- json格式,默认为Unicode编码:scrapy crawl 项目名 -o 项目名.json
- json lines格式,默认为Unicode编码:scrapy crawl 项目名 -o 项目名.jsonlines
- csv 逗号表达式,可用Excel打开:scrapy crawl 项目名 -o 项目名.csv
- xml格式:scrapy crawl 项目名 -o 项目名.xml
Parse()方法的工作机制
- 因为使用的yield,而不是return。parse函数将会被当做一个生成器使用。scrapy会逐一获取parse方法中生成的结果,并判断该结果是一个什么样的类型;
- 如果是request则加入爬取队列,如果是item类型则使用pipeline处理,其他类型则返回错误信息;
- scrapy取到第一部分的request不会立马就去发送这个request,只是把这个request放到队列里,然后接着从生成器里获取;
- 取尽第一部分的request,然后再获取第二部分的item,取到item了,就会放到对应的pipeline里处理;
- Parse()方法作为回调函数(callback)赋值给了Request,指定parse()方法来处理这些请求 scrapy.Request(url, callback=self.parse);
- Request对象经过调度,执行生成 scrapy.http.response()的响应对象,并送回给parse()方法,直到调度器中没有Request(递归的思路);
- 取尽之后,parse()工作结束,引擎再根据队列和pipelines中的内容去执行相应的操作;
- 程序在取得各个页面的items前,会先处理完之前所有的request队列里的请求,然后再提取items;
- 这一切的一切,Scrapy引擎和调度器将负责到底。
3. Scrapy 项目代码示例
3.1 setting.py:爬虫基本配置
必选配置项
1 BOT_NAME = 'tutorial' 2 3 4 5 SPIDER_MODULES = ['tutorial.spiders'] 6 7 NEWSPIDER_MODULE = 'tutorial.spiders' 8 9 10 11 ROBOTSTXT_OBEY = False # 若抓不到东西,就设置为True 12 13 14 # 取消下述注释行 15 ITEM_PIPELINES = { 16 17 'tutorial.pipelines.TutorialPipeline': 300, 18 19 }
可选配置项
# CONCURRENT_REQUESTS_PER_DOMAIN = 16 # 每个域名,同时并发的请求次数限制
# CONCURRENT_REQUESTS_PER_IP = 16 # 每个IP,同时并发的请求次数限制
# CONCURRENT_REQUESTS = 32 # 框架最大的并发请求数量,针对多个域名和多个ip的一个并发请求上限
3.2 items.py:定义您想抓取的数据
1 # Define here the models for your scraped items 2 3 # 4 5 # See documentation in: 6 7 # https://docs.scrapy.org/en/latest/topics/items.html 8 9 10 import scrapy 11 12 13 class TutorialItem(scrapy.Item): 14 15 # define the fields for your item here like: 16 17 # name = scrapy.Field() 18 19 URL = scrapy.Field() # 存放当前网页地址 20 21 TITLE = scrapy.Field() # 存放当前网页title,格式类似于:<head><title>百度一下</title></head> 22 23 H1 = scrapy.Field() # 存放一级标题 24 25 TEXT = scrapy.Field() # 存放正文
3.3 spider目录下的sohu.py:编写提取数据的Spider
- 拿到每个网页的页面源码
- 拿到网页源码中的url
- 把网页源码中要的4个数据都存到一个类似字典格式的字符串
- 把抓取的结果发给pipelines做持久化
- 把新获取的url通过递归的方式,进行抓取
1 import scrapy 2 import re,os 3 from tutorial.items import TutorialItem 4 from scrapy import Request 5 6 7 class SohuSpider(scrapy.Spider): 8 9 name = 'sohu' # 项目名称 10 # allowed_domains = ['www.sohu.com'] # 如果指定爬虫作用范围,则作用于首页之后的页面 11 start_urls = ['http://www.sohu.com/'] # 开始url 12 13 def parse(self, response): 14 # response:网页源码对象 15 # 从源码对象中获取/html下的所有标签内容,拿到了所有网页的源码 16 all_urls = re.findall('href="(.*?)"',response.xpath("/html").extract_first()) 17 for url in all_urls: 18 # 每生成这个对象,就可以存储一组4个数据 19 item = TutorialItem() 20 if re.findall("(\.jpg)|(\.jpeg)|(\.gif)|(\.ico)|(\.png)|(\.js)|(\.css)$",url.strip()): 21 pass # 去掉无效链接 22 elif url.strip().startswith("http") or url.strip().startswith("//"): 23 temp_url = url.strip() if url.strip().startswith('http') else 'http:' + url.strip() # 三目运算符获取完整网址 24 item = self.get_all(item,response) 25 # 判断item中存在正文且不为空,页面一级标题不为空 26 if 'TEXT' in item and item['TEXT'] != '' and item['TITLE'] != '': 27 yield item # 发送到管道 28 print('发送<' + temp_url + '>到下载器') # 提示 29 yield Request(temp_url,callback=self.parse) # 递归调用,实现了不断使用新的url进行下载 30 31 # 自定义封装的方法:从网页中提取4个要爬取的内容放到类似字典item的里面 32 def get_all(self,item,response): 33 # 获取当前页面的网址、title、一级标题、正文内容 34 item['URL'] = response.url.strip() 35 item['TITLE'] = response.xpath('/html/head/title/text()').extract()[0].strip() 36 contain_h1 = response.xpath('//h1/text()').extract() # 获取当前网页所有一级标题 37 contain= contain_h1[0] if len(contain_h1) !=0 else "" # 获取第一个一级标题 38 item["H1"] = contain.strip() 39 main_text = [] 40 # 遍历网页中所有p标签和br标签的内容 41 for tag in ['p','br']: 42 sub_text = self.get_content(response,tag) 43 main_text.extend(sub_text) 44 # 对正文内容去重并判断不为空 45 main_text = list(set(main_text)) 46 if len(main_text) != 0: 47 item['TEXT'] = '\n'.join(main_text) 48 return item 49 50 def get_content(self,response,tag): 51 # 判断只有大于100个文字的内容才保留 52 main_text = [] 53 contexts = response.xpath('//'+tag+'/text()').extract() 54 for text in contexts: 55 if len(text.strip()) > 100: 56 main_text.append(text.strip()) 57 return main_text
3.4 pipelines.py:将爬取后的item数据进行存储
1 # Define your item pipelines here 2 3 # 4 5 # Don't forget to add your pipeline to the ITEM_PIPELINES setting 6 7 # See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html 8 9 import json 10 11 12 class TutorialPipeline(object): 13 14 def __init__(self): 15 # 存爬取数据的文件句柄 16 self.filename = open("content.txt",'w',encoding="utf-8") 17 # 集合对象,用于去重爬取数据 18 self.contain = set() 19 20 # 数据怎么存 21 def process_item(self, item, spider): 22 # item:爬取来的数据 23 # 抓取后的数据,包含中文的话,可以直接看到正文。等价于text= str(item) 24 text = json.dumps(dict(item),ensure_ascii=False) + '\n' 25 # 把json串转换为字典 26 text_dict = eval(text) 27 # 用字典去取数据 28 if text_dict['URL'] not in self.contain: # 抓取到新网页,并写入文件 29 # 判断url是否被抓取过,如果没有被抓取过,就存到文件里面 30 for _,targetName in text_dict.items(): 31 # 实现存储数据的逻辑 32 # 网页正文包含“人”才会保存, 33 # “人”是我们想抓取页面的核心关键词,可以换成其它关键词 34 if "人" in targetName: 35 # 有的话,把这个字典写到文件里面 36 self.write_to_txt(text_dict) 37 # 避免重复写入 38 break 39 # 每次记录文件后把网页url写入集合,重复的url会自动过滤掉 40 self.contain.add(text_dict['URL']) 41 # 表示item处理完了 42 return item 43 44 45 # 爬虫关掉时,把文件关掉 46 def close_spider(self,spider): 47 self.filename.close() 48 49 # 具体把字典写入到文件的方法 50 def write_to_txt(self,text_dict): 51 52 # 把抓取到的内容写入文件中 53 for key,value in text_dict.items(): 54 self.filename.write(key+"内容:\n"+value+'\n') 55 self.filename.write(50*'='+'\n')
说明
Item 在 Spider 中被收集之后,它将会被传递到 Item Pipeline,一些组件会按照一定的顺序执行对 Item 的处理。
每个 Item Pipeline 组件(有时称之为“Item Pipeline”)是实现了简单方法的 python 类。他们接收到 Item 并通过它执行一些行为,同时也决定此 Item 是否继续通过 Pipeline,或是被丢弃而不再进行处理。
Item Pipeline 的一些典型应用:
- 清理 HTML 数据
- 验证爬取的数据(检查 Item 包含某些字段)
- 查重(并丢弃)
Item Pipeline的内置方法
每个 item pipiline 组件是一个独立的 python 类,同时必须实现以下方法:
process_item(item, spider)
每个item pipeline组件都需要调用该方法,这个方法必须返回一个 Item(或任何继承类)对象, 或是抛出 DropItem 异常,被丢弃的 item 将不会被之后的 pipeline 组件所处理。
- item (Item 对象) – 被爬取的 item
- spider (Spider 对象) – 爬取该 item 的 spider
open_spider(spider)
当 spider 被开启时,这个方法被调用。
- spider (Spider 对象) – 被开启的 spider
close_spider(spider)
当 spider 被关闭时,这个方法被调用。
- spider(Spider 对象)– 被关闭的 spider
3.5 执行结果:查看爬取数据
在根目录下执行命令:
方式一 指定json文件输出:scrapy crawl sohu -o items.json
方式二 根据pipelines.py定义输出:scrapy crawl sohu
例:E:\tutorial无限制爬取\tutorial>scrapy crawl sohu