项目地址架构组件概括调度器(Scheduler)下载器(Downloader)爬虫(Spider)实体管道(Item and Pipeline)Scrapy引擎(Engine)目录文件Selector选择器实战settings.py—文件配置item.py—定义数据实体spider.py—页面数据提取pipline.py—数据持久化
项目地址
架构
数据流图:
- 引擎打开一个网站(open a domain),找到处理该网站的Spider并向该spider请求第一个要爬取的URL(s)。
- 引擎从Spider中获取到第一个要爬取的URL并在调度器(Scheduler)以Request调度。
- 引擎向调度器请求下一个要爬取的URL。
- 调度器返回下一个要爬取的URL给引擎,引擎将URL通过下载中间件(请求(request)方向)转发给下载器(Downloader)。
- 一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过下载中间件(返回(response)方向)发送给引擎。
- 引擎从下载器中接收到Response并通过Spider中间件(输入方向)发送给Spider处理。
- Spider处理Response并返回爬取到的Item及(跟进的)新的Request给引擎。
- 引擎将(Spider返回的)爬取到的Item给Item Pipeline,将(Spider返回的)Request给调度器。
- (从第二步)重复直到调度器中没有更多地request,引擎关闭该网站。
官方架构图:
组件
概括
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,它控制着整个流程。
目录文件
scrapy.cfg :项目的配置文件
mySpider/ :项目的Python模块,将会从这里引用代码
mySpider/items.py :项目的目标文件
mySpider/pipelines.py :项目的管道文件
mySpider/settings.py :项目的设置文件
mySpider/spiders/ :存储爬虫代码目录
Selector选择器
Scrapy Selectors 内置 XPath 和 CSS 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: 写入完返回