Scrapy 爬虫项目框架
阅读原文时间:2023年07月11日阅读:1

1. Scrapy 简介

2. Scrapy 项目开发介绍

3. Scrapy 项目代码示例

3.1 setting.py:爬虫基本配置

3.2 items.py:定义您想抓取的数据

3.3 spider目录下的sohu.py:编写提取数据的Spider

3.4 pipelines.py:将爬取后的item数据进行存储

3.5 执行结果:查看爬取数据

1. Scrapy 简介

Scrapy 是一套用 python 编写的异步爬虫框架,基于 twisted 实现,运行于 linux/windows/macOS 等多种环境,具有速度快、扩展性强、使用简便等特点。

Scrapy 可以在本地运行,也能部署到云端(scrapyd)实现真正的生产级数据采集系统。

  • Scrapy Engine(引擎):Scrapy框架的核心部分,负责在Spider和ItemPipeline、Downloader、Scheduler之间通信、传递数据等。
  • Spider(爬虫):发送需要爬取的链接给引擎,最后引擎把其他模块请求回来的数据再发送给爬虫,爬虫就去解析想要的数据。这个部分是我们开发者自己写的,因为要爬取哪些链接,页面中的哪些数据是我们需要的,都是由程序员自己决定。
  • Scheduler(调度器):负责接收引擎发送过来的请求,并按照一定的方式进行排列和整理,负责调度请求的顺序等。
  • Downloader(下载器):负责接收引擎传过来的下载请求,然后去网络上下载对应的数据再交还给引擎。
  • Item Pipeline(管道):负责将Spider(爬虫)传递过来的数据进行保存。具体保存在哪里,应该看开发者自己的需求。
  • Downloader Middlewares(下载中间件):可以扩展下载器和引擎之间通信功能的中间件。
  • Spider Middlewares(Spider中间件):可以扩展引擎和爬虫之间通信功能的中间件。

Scrapy 框架的执行顺序:

  1. Spiders 的 yeild 将 request 发送给 Engine;
  2. Engine 对request 不做任何处理发送给 Scheduler;
  3. Scheduler 生成 request交给 Engine;
  4. Engine 拿到 request,通过 Middleware 进行层层过滤发送给 Downloader;
  5. Downloader 在网上获取到 response 数据之后,又经过 Middleware 进行层层过滤发送给 Engine;
  6. Engine 获取到 response 数据之后,返回给 Spiders,Spiders 的 parse() 方法对获取到的 response 数据进行处理,解析出 items 或者 requests;
  7. 将解析出来的 items 或者 requests 发送给 Engine;
  8. Engine 获取到 items 或者 requests,将 items 发送给Iitem Pipelines,将 requests 发送给Scheduler。
    注意!只有当 Scheduler 中不存在任何 request 了,整个程序才会停止(也就是说,对于下载失败的 url,scrapy 也会重新下载)。

示例:

  1. 引擎:Hi!Spider, 你要处理哪一个网站?
  2. Spider:老大要我处理xxxx.com。
  3. 引擎:你把第一个需要处理的URL给我吧。
  4. Spider:给你,第一个URL是xxxxxxx.com。
  5. 引擎:Hi!调度器,我这有request请求你帮我排序入队一下。
  6. 调度器:好的,正在处理你等一下。
  7. 引擎:Hi!调度器,把你处理好的request请求给我。
  8. 调度器:给你,这是我处理好的request。
  9. 引擎:Hi!下载器,你按照老大的下载中间件的设置帮我下载一下这个request请求。
  10. 下载器:好的!给你,这是下载好的东西。(如果失败:sorry,这个request下载失败了。然后引擎告诉调度器,这个request下载失败了,你记录一下,我们待会儿再下载)。
  11. 引擎:Hi!Spider,这是下载好的东西,并且已经按照老大的下载中间件处理过了,你自己处理一下(注意!这儿 responses 默认是交给 parse() 这个函数处理的)。
  12. Spider:(处理完毕数据之后对于需要跟进的URL)Hi!引擎,我这里有两个结果,这个是我需要跟进的URL,还有这个是我获取到的Item数据。
  13. 引擎:Hi!管道,我这儿有个item你帮我处理一下。Hi!调度器,这是需要跟进URL你帮我处理下。然后从第四步开始循环,直到获取完老大需要全部信息。
  14. 管道&调度器:好的,现在就做!

2. Scrapy 项目开发介绍

可以使用命令行来新建一个爬虫工程,这个工程会自动按照scrapy的结构创建一个工程目录。

  1. 创建项目:scrapy startproject xxx(项目名字,不区分大小写)
  2. 明确目标 (编写items.py):明确你想要抓取的目标
  3. 制作爬虫 (spiders/xxspider.py):制作爬虫开始爬取网页
  4. 存储内容 (pipelines.py):设计管道存储爬取内容
  5. 启动程序的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)

以上三步执行完毕,则工程的框架建立完毕。后续需要你自己实现爬虫分析和保存的逻辑。目录结构如下图:

  • json格式,默认为Unicode编码:scrapy crawl 项目名 -o 项目名.json
  • json lines格式,默认为Unicode编码:scrapy crawl 项目名 -o 项目名.jsonlines
  • csv 逗号表达式,可用Excel打开:scrapy crawl 项目名 -o 项目名.csv
  • xml格式:scrapy crawl 项目名 -o 项目名.xml
  1. 因为使用的yield,而不是return。parse函数将会被当做一个生成器使用。scrapy会逐一获取parse方法中生成的结果,并判断该结果是一个什么样的类型;
  2. 如果是request则加入爬取队列,如果是item类型则使用pipeline处理,其他类型则返回错误信息;
  3. scrapy取到第一部分的request不会立马就去发送这个request,只是把这个request放到队列里,然后接着从生成器里获取;
  4. 取尽第一部分的request,然后再获取第二部分的item,取到item了,就会放到对应的pipeline里处理;
  5. Parse()方法作为回调函数(callback)赋值给了Request,指定parse()方法来处理这些请求 scrapy.Request(url, callback=self.parse);
  6. Request对象经过调度,执行生成 scrapy.http.response()的响应对象,并送回给parse()方法,直到调度器中没有Request(递归的思路);
  7. 取尽之后,parse()工作结束,引擎再根据队列和pipelines中的内容去执行相应的操作;
  8. 程序在取得各个页面的items前,会先处理完之前所有的request队列里的请求,然后再提取items;
  9. 这一切的一切,Scrapy引擎和调度器将负责到底。

3. Scrapy 项目代码示例

必选配置项

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的一个并发请求上限

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,格式类似于:百度一下
22
23 H1 = scrapy.Field() # 存放一级标题
24
25 TEXT = scrapy.Field() # 存放正文

  1. 拿到每个网页的页面源码

  2. 拿到网页源码中的url

  3. 把网页源码中要的4个数据都存到一个类似字典格式的字符串

  4. 把抓取的结果发给pipelines做持久化

  5. 把新获取的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

    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

在根目录下执行命令:

方式一 指定json文件输出:scrapy crawl sohu -o items.json

方式二 根据pipelines.py定义输出:scrapy crawl sohu

例:E:\tutorial无限制爬取\tutorial>scrapy crawl sohu