😂

Scrapy

 

项目地址

架构

数据流图:
notion image
  1. 引擎打开一个网站(open a domain),找到处理该网站的Spider并向该spider请求第一个要爬取的URL(s)。
  1. 引擎从Spider中获取到第一个要爬取的URL并在调度器(Scheduler)以Request调度。
  1. 引擎向调度器请求下一个要爬取的URL。
  1. 调度器返回下一个要爬取的URL给引擎,引擎将URL通过下载中间件(请求(request)方向)转发给下载器(Downloader)。
  1. 一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过下载中间件(返回(response)方向)发送给引擎。
  1. 引擎从下载器中接收到Response并通过Spider中间件(输入方向)发送给Spider处理。
  1. Spider处理Response并返回爬取到的Item及(跟进的)新的Request给引擎。
  1. 引擎将(Spider返回的)爬取到的Item给Item Pipeline,将(Spider返回的)Request给调度器。
  1. (从第二步)重复直到调度器中没有更多地request,引擎关闭该网站。
官方架构图:
notion image
 

组件

概括

Scrapy框架主要由五大组件组成,它们分别是调度器(Scheduler)、下载器(Downloader)、爬虫(Spider)和实体管道(Item Pipeline)、Scrapy引擎(Scrapy Engine)以及两个中间件: 爬虫中间件(Spider Middlewares)和•调度中间件(Scheduler Middewares)。

调度器(Scheduler)

假设成URL(抓取网页的连接)的优先队列,由它来决定下一个要抓取的网站是什么,同时去除重复的网站,用户可以根据自己的需求定制调度器

下载器(Downloader)

下载器是所有组件中负担最大的,用于高效的下载网络上的资源。Scrapy的下载器代码不会太复杂,但效率高,主要的原因是Scrapy下载器是建立在twisted这个高效的异步模型上的(其实整个框架都在建立在这个模型上的)。

爬虫(Spider)

爬虫,是用户最关心的部份。用户定制自己的爬虫(通过定制正则表达式等语法),用于从特定的网页中提取自己需要的信息,即所谓的实体(Item)。 用户也可以从中提取出链接,让Scrapy继续抓取下一个页面。

实体管道(Item and Pipeline)

用与定义和处理spider提取的实体,主要功能为持久化实体、验证实体的有效性、清除不需要的信息

Scrapy引擎(Engine)

Scrapy引擎是整个框架的核心.它用来控制调试器、下载器、爬虫。实际上,引擎相当于计算机的CPU,它控制着整个流程。

目录文件

notion image
scrapy.cfg :项目的配置文件 mySpider/ :项目的Python模块,将会从这里引用代码 mySpider/items.py :项目的目标文件 mySpider/pipelines.py :项目的管道文件 mySpider/settings.py :项目的设置文件 mySpider/spiders/ :存储爬虫代码目录

Selector选择器

Scrapy Selectors 内置 XPathCSS Selector 表达式机制
Selector有四个基本的方法,最常用的还是xpath
  • xpath(): 传入xpath表达式,返回该表达式所对应的所有节点的selector list列表
  • extract(): 序列化该节点为字符串并返回list
  • css(): 传入CSS表达式,返回该表达式所对应的所有节点的selector list列表,语法同 BeautifulSoup4
  • re(): 根据传入的正则表达式对数据进行提取,返回字符串list列表

实战

settings.py—文件配置

# 项目名 BOT_NAME = 'sina' SPIDER_MODULES = ['sina.spiders'] NEWSPIDER_MODULE = 'sina.spiders' # 避免程序运行时打印log日志信息 LOG_LEVEL = 'WARNING' # 是否遵循机器人下而已 ROBOTSTXT_OBEY = False # 定义请求头 DEFAULT_REQUEST_HEADERS = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:61.0) Gecko/20100101 Firefox/61.0', 'Cookie': "_T_WM=6e9d2a91353e68e32fac5e697c7a4a9b; SUBP=0033WrSXqPxfM725Ws9jqgMF55529P9D9W5lxZkfRv9D7SG.7bAfVFDv5NHD95QES0zf1he0ehnfWs4Dqcj1BXH2xLSWxLS2-XxWxJ2t; SCF=AmXp13HXbCUXzP216Sjbo6hv-EKz4K-oc_ojo3tIzO-tYF54UXYEeGyuAL18vMbVMduFpqQAiRf-DaAtpIxAcRc.; SUB=_2A25y80H_DeRhGeRJ6VcZ8y3MwzmIHXVuHG-3rDV6PUJbktAKLRbnkW1NUk6ZTHEYTNqde9sIxZ8UPjEaLxSXBMot"} # 最大并发数 CONCURRENT_REQUESTS = 100 # 下载延迟时间 DOWNLOAD_DELAY = 0.1 # 下载器中间件组件 DOWNLOADER_MIDDLEWARES = { 'weibo.middlewares.UserAgentMiddleware': None, 'scrapy.downloadermiddlewares.cookies.CookiesMiddleware': None, 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware': None } # 打开管道 越低越爬取的优先度越高 ITEM_PIPELINES = { 'sina.pipelines.MySQLPipeline': 300, }
详细配置教程:

item.py—定义数据实体

import scrapy class ZcoolItem(scrapy.Item): # define the fields for your item here like: imgLink = scrapy.Field() # 封面图片链接 title = scrapy.Field() # 标题 types = scrapy.Field() # 类型 vistor = scrapy.Field() # 人气 comment = scrapy.Field() # 评论数 likes = scrapy.Field() # 推荐人数
item定义你要提取的内容(定义数据结构),Field方法实际上的做法是创建一个字典,给字典添加一个建,暂时不赋值,等待提取数据后再赋值。

spider.py—页面数据提取

from zcool.items import ZcoolItem def parse(self, response): divList = response.xpath('//div[@class="work-list-box"]/div') for div in divList: imgLink = div.xpath("./div[1]/a/img/@src").extract()[0] # 1.封面图片链接 ... 2.title(标题);3 types(类型);4vistor(人气);5comment(评论数) .... likes = div.xpath("./div[2]/p[3]/span[3]/@title").extract_first() # 6likes(推荐人数) item = ZcoolItem(imgLink=imgLink,title=title,types=types,vistor=vistor,comment=comment,likes=likes) yield item
item实例创建:
这里我们之前在目录文件配置的item文件中已经进行了设置,对于数据存储,我们在爬虫文件中开头要导入这个类,然后使用yield返回数据
xpsth提取数据方法:
S.N.
方法 & 描述
extract()
返回的是符合要求的所有的数据,存在一个列表里。
extract_first()
返回的hrefs 列表里的第一个数据。
get()
和extract_first()方法返回的是一样的,都是列表里的第一个数据。
getall()
和extract()方法一样,返回的都是符合要求的所有的数据,存在一个列表里。
翻页实现方法:
next_href = response.xpath("//a[@class='laypage_next']/@href").extract_first() if next_href: next_url = response.urljoin(next_href) print('*' * 60) print(next_url) print('*' * 60) request = scrapy.Request(next_url) yield request
在for循环完毕后,通过scrapy.Request()把下一页的url传递到Request函数,进行翻页循环数据采集
count = 1 class ZcSpider(scrapy.Spider): name = 'zc' allowed_domains = ['zcool.com.cn'] start_urls = ['https://www.zcool.com.cn/home?p=1#tab_anchor'] # 第一页的url def parse(self, response): global count count += 1 for div in divList: # ...xxx... yield item next_url = 'https://www.kuaikanmanhua.com/tag/0?state=1&sort=1&page={}'.format(count) yield scrapy.Request(next_url)
定义一个全局变量count = 0,每爬取一页数据,令其加一,构建新的url,再使用scrapy.Request() 发起请求。
yield和return:
不能使用return这个无容置疑,因为要翻页,使用return直接退出函数;而对于yield:在调用for的时候,函数内部不会立即执行,只是返回了一个生成器对象。在迭代的时候函数会开始执行,当在yield的时候,会返回当前值(i)。之后的这个函数会在循环中进行,直到没有下一个值。

pipline.py—数据持久化

from itemadapter import ItemAdapter import csv class ZcoolPipeline: def __init__(self): self.f = open('Zcool.csv','w',encoding='utf-8',newline='') # line1 self.file_name = ['imgLink', 'title','types','vistor','comment','likes'] # line2 self.writer = csv.DictWriter(self.f, fieldnames=self.file_name) # line3 self.writer.writeheader() # line4 def process_item(self, item, spider): self.writer.writerow(dict(item)) # line5 print(item) return item # line6 def close_spider(self,spider): self.f.close()
代码解释:
  • line1: 打开文件,指定方式为写,利用第3个参数把csv写数据时产生的空行消除
  • line2: 设置文件第一行的字段名,注意要跟spider传过来的字典key名称相同
  • line3: 指定文件的写入方式为csv字典写入,参数1为指定具体文件,参数2为指定字段名
  • line4: 写入第一行字段名,因为只要写入一次,所以文件放在__init__里面
  • line5: 写入spider传过来的具体数值,注意在spider文件中yield的item,是一个由类创建的实例对象,我们写入数据时,写入的是 字典,所以这里还要转化一下。
  • line6: 写入完返回