- 9 mins

爬虫之 Scrapy 框架

Table of Contents

☞ Scrapy 文件详解

Scrapy 简介

Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,使用Twisted异步网络库处理网络通讯,爬取网站数据,提取结构性数据的应用框架,可以应用在数据挖掘,信息处理,监测和自动化测试或存储历史数据等一系列的程序中

主要组件

 - 引擎(Scrapy):用来处理整个系统的数据流处理, 触发事务(框架核心)
 - 调度器(Scheduler):用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回.  可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址
 - 下载器(Downloader):用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的)
 - 爬虫(Spiders):爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item) 。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面
 - 项目管道(Pipeline):负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被 爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据
 - 下载器中间件(Downloader Middlewares) :位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应
 - 爬虫中间件(Spider Middlewares):介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出
 - 调度中间件(Scheduler Middewares):介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应

工作流程

 1. 引擎从调度器中取出一个链接(URL)用于接下来的抓取
 2. 引擎把URL封装成一个请求(Request)传给下载器
 3. 下载器把资源下载下来,并封装成应答包(Response)
 4. 爬虫解析Response
 5. 解析出实体(Item),则交给实体管道进行进一步的处理
 6. 解析出的是链接(URL),则把URL交给调度器等待抓取

安装

Linux & Mac

 pip3 install scrapy

Windows

 pip3 install wheel

 - 下载twisted
 http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted

 - 进入下载目录
 pip3 install Twisted-17.5.0-cp35-cp35m-win_amd64.whl

 - 安装 scrapy
 pip3 install scrapy

 - 下载并安装pywin32
 https://sourceforge.net/projects/pywin32/files/

基本命令

 scrapy startproject [项目名称]		 # 创建项目
 scrapy genspider [-t template] <name> <domain>		 #创建爬虫应用
 scrapy list	 #展示爬虫应用列表
 scrapy crawl [爬虫应用名称]  #运行单独爬虫应用
 scrapy genspider -l	 #查看所有命令
 scrapy genspider -d [模板名称]  #查看模板命令

HtmlXpathSelector

简介

HtmlXpathSelector 是 Scrapy 自有的用于处理HTML文档的选择器,被称为XPath选择器(或简称为“选择器”),是因为它们“选择”由XPath表达式指定的HTML文档的某些部分。

其它选择器

应用

 from scrapy.selector import Selector, HtmlXPathSelector
 from scrapy.http import HtmlResponse
 html = """
  	<!DOCTYPE html>
 		<html>
 		<head lang="en">
 			<meta charset="UTF-8">
 			<title></title>
 		</head>
 		<body>
 			<ul>
 				<li class="item-"><a id='i12' href="link.html">first item</a></li>
 				<li class="item-0"><a id='i2' href="llink.html">first item</a></li>
 				<li class="item-1"><a href="llink2.html">second item<span>vv</span></a></li>
 			</ul>
 			<div><a href="llink2.html">second item</a></div>
 		</body>
 	</html>
 """
 response = HtmlResponse(url='http://example.com', body=html,encoding='utf-8')
 hxs = Selector(response)  

 #取出所有a标签
 hxs = Selector(response=response).xpath('//a')

 #取出所有有id属性的a标签
 hxs = Selector(response=response).xpath('//a[@id]')  

 #取出开头为link的href标签
 hxs = Selector(response=response).xpath('//a[starts-with(@href,"link")]' )

 #取出链接包含link的href标签
 hxs = Selector(response=response).xpath('//a[contains(@href, "link")]')

 #正则,取出i开头后边是数字的
 hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]')    

 hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]/text()').extract()
 hxs = Selector(response=response).xpath('//a[re:test(@id, "i\d+")]/@href').extract()
 hxs = Selector(response=response).xpath('/html/body/ul/li/a/@href').extract()
 hxs = Selector(response=response).xpath('//body/ul/li/a/@href').extract_first()

 #参考:https://doc.scrapy.org/en/0.12/topics/selectors.html

写个基于 Scrapy 的爬虫项目

项目结构

 cutetopaz/
 	scrapy.cfg				#项目的主配置信息(真正爬虫相关的配置信息在settings.py文件中)
 	cutetopaz/				
 		__init__.py
 		items.py			#设置数据存储模板,用于结构化数据 如:Django的Model
 		pipelines.py		#数据处理行为 如:一般结构化的数据持久化
 		settings.py			#配置文件 如:递归的层数、并发数,延迟下载等
 		spiders/			#爬虫目录 如:创建文件,编写爬虫规则
 			__init__.py
 			xiaohuar.py		#爬虫文件,一般正常人以网站域名命名

xiaohuar.py

 import scrapy
 import hashlib
  scrapy.selector import Selector
  scrapy.http.request import Request
  scrapy.http.cookies import CookieJar
  scrapy import FormRequest
  ..items import XiaoHuarItem

 class XiaoHuarSpider(scrapy.Spider):
 	name = "hira"
 	allowed_domains = ["xiaohuar.com"]
 	start_urls = ["http://www.xiaohuar.com/list-1-1.html",]
 	has_request_set = {}
 	def parse(self, response):
 		hxs = Selector(response)
 		items = hxs.select('//div[@class="item_list infinite_scroll"]/div')
 		for item in items:
 			src = item.xpath('.//div[@class="img"]/a/img/@src').extract_first()
 			name = item.xpath('.//div[@class="img"]/span/text()').extract_first()
 			school = item.xpath('.//div[@class="img"]/div[@class="btns"]/a/text()').extract_first()    	#==>广西大学
 			url = "http://www.xiaohuar.com%s" % src
 			obj = XiaoHuarItem(name=name,school=school, url=url)
 			yield obj
 		urls = hxs.xpath('//a[re:test(@href, "http://www.xiaohuar.com/	list-1-\d+.html")]/@href').extract()    #拿到所有页面
 		for url in urls:
 			print("here is url:",url)
 			key = self.md5(url)
 			print('key!!!!!',key)
 			if key in self.has_request_set:
 				# print('你要的大字典',self.has_request_set)
 				pass
 			else:
 				self.has_request_set[key] = url
 				req = Request(url=url,method='GET',callback=self.parse)
 				yield req
 	@staticmethod
 	def md5(val):
 		ha = hashlib.md5()
 		ha.update(bytes(val, encoding='utf-8'))
 		key = ha.hexdigest()
 		return key

items.py

 import scrapy
 class CutetopazItem(scrapy.Item):
 	# define the fields for your item here like:
 	# name = scrapy.Field()
 	pass

 class XiaoHuarItem(scrapy.Item):
 	name = scrapy.Field()
 	school = scrapy.Field()
 	url = scrapy.Field()

pipelines.py

 import json
 import os
 import requests
 class JsonPipeline(object):
 	'''按照settings.py里的顺序,先执行这个'''
 	def __init__(self):
 		self.file = open('xiaohua.txt', 'w')
 	def process_item(self, item, spider):
 		v = json.dumps(dict(item), ensure_ascii=False)  #item ==> <class 'cutetopaz.items.XiaoHuarItem'>  dict(item) ==> <class 'dict'> ,肉眼看的话item就是个字典啊,去掉dict()报错is not JSON serializable
 		self.file.write(v)
 		self.file.write('\n')
 		self.file.flush()
 		return item     #返回一个json序列化后的item
 class FilePipeline(object):
 	def __init__(self):
 		if not os.path.exists('imgs'):      #创建个文件夹
 			os.makedirs('imgs')
 	def process_item(self, item, spider):
 		response = requests.get(item['url'], stream=True)
 		print('看看',response.__attrs__)
 		file_name = '%s_%s.jpg' % (item['name'], item['school'])
 		with open(os.path.join('imgs', file_name), mode='wb') as f:
 			f.write(response.content)
 		return item

settings.py

 ITEM_PIPELINES = {
 	'cutetopaz.pipelines.JsonPipeline': 100,
 	'cutetopaz.pipelines.FilePipeline': 300,
 }

Scrapy 自定义

自定制命令

 1.在spiders同级创建任意目录,如:commands
 2.在其中创建 crawlall.py 文件 (此处文件名就是自定义的命令)
 3.settings.py 中添加配置 COMMANDS_MODULE = '项目名称.目录名称'
 4.项目目录中执行命令:scrapy crawlall

代码:

 from scrapy.commands import ScrapyCommand
 from scrapy.utils.project import get_project_settings
 class Command(ScrapyCommand):
 	requires_project = True
 	def syntax(self):
 		return '[options]'
 	def short_desc(self):
 		return 'Runs all of the spiders'
 	def run(self, args, opts):
 		spider_list = self.crawler_process.spiders.list()
 		for name in spider_list:
 			self.crawler_process.crawl(name, **opts.__dict__)
 		self.crawler_process.start()

自定义扩展

自定义扩展时,利用信号在指定位置注册制定操作

 from scrapy import signals
 class MyExtension(object):
 	def __init__(self, value):
 		self.value = value			
 	@classmethod
 	def from_crawler(cls, crawler):
 		val = crawler.settings.getint('MMMM')
 		ext = cls(val)			
 		crawler.signals.connect(ext.spider_opened, signal=signals.spider_opened)
 		crawler.signals.connect(ext.spider_closed, signal=signals.spider_closed)
 		return ext
 	def spider_opened(self, spider):
 		print('open')		
 	def spider_closed(self, spider):
 		print('close')

避免重复访问

Scrapy 默认使用 scrapy.dupefilter.RFPDupeFilter 进行去重,相关配置有

 DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter'
 DUPEFILTER_DEBUG = False
 JOBDIR = "保存范文记录的日志路径,如:/root/"  # 最终路径为 /root/requests.seen
Topaz

Topaz

Always keep learning.

comments powered by Disqus
rss facebook twitter github youtube mail spotify instagram linkedin google pinterest medium vimeo