Scrapy
Scrapy,Python开发的一个快速、高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试。
Scrapy吸引人的地方在于它是一个框架,任何人都可以根据需求方便的修改。它也提供了多种类型爬虫的基类,如BaseSpider、sitemap爬虫等,最新版本又提供了web2.0爬虫的支持。
Scrap,是碎片的意思,这个Python的爬虫框架叫Scrapy。
Scrapy主要包括了以下组件:
-
引擎(Scrapy)
用来处理整个系统的数据流处理, 触发事务(框架核心) -
调度器(Scheduler)
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL(抓取网页的网址或者说是链接)的优先队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址 -
下载器(Downloader)
用于下载网页内容, 并将网页内容返回给蜘蛛(Scrapy下载器是建立在twisted这个高效的异步模型上的) -
爬虫(Spiders)
爬虫是主要干活的, 用于从特定的网页中提取自己需要的信息, 即所谓的实体(Item)。用户也可以从中提取出链接,让Scrapy继续抓取下一个页面 -
项目管道(Pipeline)
负责处理爬虫从网页中抽取的实体,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。 -
下载器中间件(Downloader Middlewares)
位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。 -
爬虫中间件(Spider Middlewares)
介于Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出。 -
调度中间件(Scheduler Middewares)
介于Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。
Scrapy运行流程如下:
- 引擎从调度器中取出一个链接(URL)用于接下来的抓取
-
引擎把URL封装成一个请求(Request)传给下载器
-
下载器把资源下载下来,并封装成应答包(Response)
-
爬虫解析Response
-
解析出实体(Item),则交给实体管道进行进一步的处理
- 解析出的是链接(URL),则把URL交给调度器等待抓取
安装教程
1 Linux 2 pip3 install scrapy 3 Windows 4 a. pip3 install wheel 5 b. 下载twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted 6 c. 进入下载目录,执行 pip3 install Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whl 7 d. pip3 install scrapy 8 e. 下载并安装pywin32:https://sourceforge.net/projects/pywin32/files/
新建项目
1 scrapy startproject xxxxx xxxxx为新建的项目名称 2 scrapy genspider <name> <domain> 3 例如 4 scrapy genspider chouti chouti.com
操作项目
1 cd进入xxxxx的文件夹 2 3 scrapy crawl 爬虫应用名称
新建Scrapy的目录内容
1 project_name/ 2 scrapy.cfg 3 project_name/ 4 __init__.py 5 items.py 6 pipelines.py 7 settings.py 8 spiders/ 9 __init__.py 10 project_name.py
下面解释一下文件
-
scrapy.cfg 项目的主配置信息。(真正爬虫相关的配置信息在settings.py文件中)
-
items.py 设置数据存储模板,用于结构化数据。
-
pipelines 数据处理行为,如:一般结构化的数据持久化
-
settings.py 配置文件,如:递归的层数、并发数,延迟下载等
- spiders 爬虫目录,如:创建文件,编写爬虫规则
选择器的使用
1 from scrapy.selector import Selector, HtmlXPathSelector
选择器规则
nodeName 选取此节点的所有节点 / 从根节点选取 // 从匹配选择的当前节点选择文档中的节点,不考虑它们的位置 . 选择当前节点 .. 选取当前节点的父节点 @ 选取属性 * 匹配任何元素节点 @* 匹配任何属性节点 Node() 匹配任何类型的节点
具体语法
1 //div[id="Test"] #查找id为Test的标签 2 //a[re:test(@herf, '/all/hot/\d+')] # 正则表达式查找a标签 3 .//h3/text() # h3标签的文本内容 4 .//div[@class='part1']//a[1]/@href # a标签的href文本
extract_first() 返回一个字符串
extract() 返回一个数组
携带cookies
1 # 方法一 2 print(response.headers.getlist('Set-Cookie')) 3 # 解析之后的cookie 4 cookie_dict = {} 5 cookie_jar = CookieJar() 6 cookie_jar.extract_cookies(response, response.request) 7 for k, v in cookie_jar._cookies.items(): 8 for i, j in v.items(): 9 for m, n in j.items(): 10 self.cookie_dict[m] = n.value 11 print(self.cookie_dict) 12 13 # 方法二,在Request对象添加meta={'cookiejar': True} 14 yield Request(url=url, callback=self.login, meta={'cookiejar': True})
请求头-可在下载中间件添加
1 from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware 2 3 class MyAgent(UserAgentMiddleware): 4 def __init__(self,user_agent=''): 5 self.user_agent = user_agent 6 7 def process_request(self,request,spider): 8 request.headers.setdefault('Host','www.jianshu.com') 9 request.headers.setdefault('User-Agent','Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/53.0.2785.143 Chrome/53.0.2785.143 Safari/537.36') 10 11 配置文件走一下 12 DOWNLOADER_MIDDLEWARES = { 13 'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware' : None,#必需 ,禁用默认的middleware 14 'Test.middlewares.MyAgent': 543, 15 }
去重
1 DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter' 2 DUPEFILTER_DEBUG = False 3 JOBDIR = "保存范文记录的日志路径,如:/root/" # 最终路径为 /root/requests.seen
自定义去重
1 class RepeatUrl: 2 def __init__(self): 3 self.visited_url = set() 4 5 @classmethod 6 def from_settings(cls, settings): 7 """ 8 初始化时,调用 9 :param settings: 10 :return: 11 """ 12 return cls() 13 14 def request_seen(self, request): 15 """ 16 检测当前请求是否已经被访问过 17 :param request: 18 :return: True表示已经访问过;False表示未访问过 19 """ 20 if request.url in self.visited_url: 21 return True 22 self.visited_url.add(request.url) 23 return False 24 25 def open(self): 26 """ 27 开始爬去请求时,调用 28 :return: 29 """ 30 print('open replication') 31 32 def close(self, reason): 33 """ 34 结束爬虫爬取时,调用 35 :param reason: 36 :return: 37 """ 38 print('close replication') 39 40 def log(self, request, spider): 41 """ 42 记录日志 43 :param request: 44 :param spider: 45 :return: 46 """ 47 print('repeat', request.url)
爬虫中间件
1 class SpiderMiddleware(object): 2 3 def process_spider_input(self,response, spider): 4 """ 5 下载完成,执行,然后交给parse处理 6 :param response: 7 :param spider: 8 :return: 9 """ 10 pass 11 12 def process_spider_output(self,response, result, spider): 13 """ 14 spider处理完成,返回时调用 15 :param response: 16 :param result: 17 :param spider: 18 :return: 必须返回包含 Request 或 Item 对象的可迭代对象(iterable) 19 """ 20 return result 21 22 def process_spider_exception(self,response, exception, spider): 23 """ 24 异常调用 25 :param response: 26 :param exception: 27 :param spider: 28 :return: None,继续交给后续中间件处理异常;含 Response 或 Item 的可迭代对象(iterable),交给调度器或pipeline 29 """ 30 return None 31 32 33 def process_start_requests(self,start_requests, spider): 34 """ 35 爬虫启动时调用 36 :param start_requests: 37 :param spider: 38 :return: 包含 Request 对象的可迭代对象 39 """ 40 return start_requests
1 class DownMiddleware1(object): 2 def process_request(self, request, spider): 3 """ 4 请求需要被下载时,经过所有下载器中间件的process_request调用 5 :param request: 6 :param spider: 7 :return: 8 None,继续后续中间件去下载; 9 Response对象,停止process_request的执行,开始执行process_response 10 Request对象,停止中间件的执行,将Request重新调度器 11 raise IgnoreRequest异常,停止process_request的执行,开始执行process_exception 12 """ 13 pass 14 15 16 17 def process_response(self, request, response, spider): 18 """ 19 spider处理完成,返回时调用 20 :param response: 21 :param result: 22 :param spider: 23 :return: 24 Response 对象:转交给其他中间件process_response 25 Request 对象:停止中间件,request会被重新调度下载 26 raise IgnoreRequest 异常:调用Request.errback 27 """ 28 print('response1') 29 return response 30 31 def process_exception(self, request, exception, spider): 32 """ 33 当下载处理器(download handler)或 process_request() (下载中间件)抛出异常 34 :param response: 35 :param exception: 36 :param spider: 37 :return: 38 None:继续交给后续中间件处理异常; 39 Response对象:停止后续process_exception方法 40 Request对象:停止中间件,request将会被重新调用下载 41 """ 42 return None
设置代理
1 from scrapy.contrib.downloadermiddleware.httpproxy import HttpProxyMiddleware 2 3 方式一:使用默认 4 os.environ 5 { 6 http_proxy:http://root:woshiniba@192.168.11.11:9999/ 7 https_proxy:http://192.168.11.11:9999/ 8 } 9 方式二:使用自定义下载中间件 10 11 def to_bytes(text, encoding=None, errors='strict'): 12 if isinstance(text, bytes): 13 return text 14 if not isinstance(text, six.string_types): 15 raise TypeError('to_bytes must receive a unicode, str or bytes ' 16 'object, got %s' % type(text).__name__) 17 if encoding is None: 18 encoding = 'utf-8' 19 return text.encode(encoding, errors) 20 21 class ProxyMiddleware(object): 22 def process_request(self, request, spider): 23 PROXIES = [ 24 {'ip_port': '111.11.228.75:80', 'user_pass': ''}, 25 {'ip_port': '120.198.243.22:80', 'user_pass': ''}, 26 {'ip_port': '111.8.60.9:8123', 'user_pass': ''}, 27 {'ip_port': '101.71.27.120:80', 'user_pass': ''}, 28 {'ip_port': '122.96.59.104:80', 'user_pass': ''}, 29 {'ip_port': '122.224.249.122:8088', 'user_pass': ''}, 30 ] 31 proxy = random.choice(PROXIES) 32 if proxy['user_pass'] is not None: 33 request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port']) 34 encoded_user_pass = base64.encodestring(to_bytes(proxy['user_pass'])) 35 request.headers['Proxy-Authorization'] = to_bytes('Basic ' + encoded_user_pass) 36 print "**************ProxyMiddleware have pass************" + proxy['ip_port'] 37 else: 38 print "**************ProxyMiddleware no pass************" + proxy['ip_port'] 39 request.meta['proxy'] = to_bytes("http://%s" % proxy['ip_port']) 40 41 配置文件添加 42 DOWNLOADER_MIDDLEWARES = { 43 'step8_king.middlewares.ProxyMiddleware': 500, 44 }
设置证书
1 Https访问 2 Https访问时有两种情况: 3 1. 要爬取网站使用的可信任证书(默认支持) 4 DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory" 5 DOWNLOADER_CLIENTCONTEXTFACTORY = "scrapy.core.downloader.contextfactory.ScrapyClientContextFactory" 6 7 2. 要爬取网站使用的自定义证书 8 DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory" 9 DOWNLOADER_CLIENTCONTEXTFACTORY = "step8_king.https.MySSLFactory" 10 11 # https.py 12 from scrapy.core.downloader.contextfactory import ScrapyClientContextFactory 13 from twisted.internet.ssl import (optionsForClientTLS, CertificateOptions, PrivateCertificate) 14 15 class MySSLFactory(ScrapyClientContextFactory): 16 def getCertificateOptions(self): 17 from OpenSSL import crypto 18 v1 = crypto.load_privatekey(crypto.FILETYPE_PEM, open('/Users/wupeiqi/client.key.unsecure', mode='r').read()) 19 v2 = crypto.load_certificate(crypto.FILETYPE_PEM, open('/Users/wupeiqi/client.pem', mode='r').read()) 20 return CertificateOptions( 21 privateKey=v1, # pKey对象 22 certificate=v2, # X509对象 23 verify=False, 24 method=getattr(self, 'method', getattr(self, '_ssl_method', None)) 25 )
配置文件详解
1 BOT_NAME = 'news' # 爬虫的名字 会放在USER_AGENT中携带到请求里 2 3 SPIDER_MODULES = ['news.spiders'] # 爬虫的路径 4 NEWSPIDER_MODULE = 'news.spiders' # 爬虫的路径 5 6 # USER_AGENT = 'news (+http://www.yourdomain.com)' # 同http请求的请求头中User-Agent 7 USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 UBrowser/6.2.3964.2 Safari/537.36' 8 9 ROBOTSTXT_OBEY = True # 是否遵守爬虫的规则,如果遵守会检测网站中配置文件中是否允许爬虫 10 11 CONCURRENT_REQUESTS = 32 # 请求并发数,默认并发16个请求 12 13 DOWNLOAD_DELAY = 3 # 下载延迟,表示每个请求访问前的阻塞3秒 14 CONCURRENT_REQUESTS_PER_DOMAIN = 16 # 针对每个域名并发请求数 15 CONCURRENT_REQUESTS_PER_IP = 16 # 针对每个IP的并发请求数 16 17 COOKIES_ENABLED = True # 是否爬取cookie, 默认为True 18 COOKIES_DEBUG = True # 开启cookie的debug模式,每次访问都会打印cookie 19 20 21 TELNETCONSOLE_ENABLED = False # 监听6023端口,可以远程查看爬虫的情况"telnet 127.0.0.1" 22 23 DEFAULT_REQUEST_HEADERS = { # http请求的请求头 24 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 25 'Accept-Language': 'zh-CN,zh;q=0.8', 26 } 27 28 SPIDER_MIDDLEWARES = { 29 'news.middlewares.NewsSpiderMiddleware': 543, 30 } 31 32 EXTENSIONS = { # 爬虫的信号注册到此选项 33 # 'scrapy.extensions.telnet.TelnetConsole': None, 34 'news.extensions.MyExtensions':300 35 } 36 37 ITEM_PIPELINES = { # 爬虫的pipline注册到此选项 38 'news.pipelines.NewsPipeline': 300, 39 } 40 41 SCHEDULER = 'scrapy.core.scheduler.Scheduler' # 调度器队列 42 43 DEPTH_LIMIT = 1 # 爬虫爬取深度 44 45 DEPTH_PRIORITY = 0 # 1:广度优先,0:深度优先 46 47 DUPEFILTER_CLASS = 'news.duplicatioe.MyDupeFilter' # url重复过滤器
个人总结
- 对于这次scrapy的初步了解,更多的去了解scrapy框架的源码,因为只有源码才是一切的起源
- scrapy是一个分布式的爬虫框架,每爬取得到一个item就直接送到pipeline中进行处理,而不是等所有item都爬取完后再进行处理,这一点要注意。
- 对于调度器和各种中间件,在setting配置文件要添加
- Request是一个封装用户请求的类,在回调函数中yield该对象表示继续访问
- HtmlXpathSelector用于结构化HTML代码并提供选择器功能
- setting的数字越大,权重越大,先执行