66.1-scrapy框架概述和编程流程
总结:
- CSS不用写当前(//); Xpath要写当前(./);
- extract_first() / extract()[0] ; 提取data 结果 ;
- 调度器(Scheduler)中自带去重功能;
- 拿到的URL有相对和绝对之分; /tag/编程?start=20&type=T - / 为根开始
Scrapy框架
Scrapy是用Python实现的一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘、信息处理或存储历史数据等一系列的程序中。
Scrapy使用Twisted基于事件的高效异步网络框架来处理网络通信,可以加快下载速度,不用自己去实现异步框架,并且包含了各种中间件接口,可以灵活的完成各种需求。
1. Scrapy架构
Scrapy的组件主要分为:
1.引擎(Scrapy Engine):
该组件是Scrapy的核心组件,用于请求(Request)、响应(Response)、数据(Item)和信号(Signal)的转发和调度等,其余组件之间的数据交互均由引擎负责;2.调度器(Scheduler):管URL,没有URL就停下来了;
存放未发出的请求(Request),将其加入队列并按照权重调整顺序,以便之后引擎请求他们时提供给引擎。
初始的爬取URL和后续在页面中获取的待爬取的**URL将放入调度器中,等待爬取。同时调度器会自动去除重复的URL(如果特定的URL不需要去重也可以通过设置实现,如post请求的URL)3.爬虫(Spider):
Spider是编写的类,作用如下:
Scrapy用户编写用于分析response(调度器)并提取item(即获取到的item-pipline)
额外跟进的URL,将额外跟进的URL提交给引擎,加入到Scheduler调度器中。将每个spider负责处理一个特定(或一些)网站。4.数据管道(Pipelines):
Item Pipeline负责处理被spider提取出来的item。典型的处理有清理、 验证及持久化(例如存取到数据库中)。当页面被爬虫解析所需的数据存入Item后,将被发送到项目管道(Pipeline),并经过设置好次序的pipeline程序处理这些数据,最后将存入本地文件或存入数据库。
- 清理HTML数据
- 验证爬取的数据(检查item包含某些字段)
- 查重(或丢弃)
- 使用pipeline文件将数据持久化,存储到json文件,后期再存储到数据库中。
5.下载器(Downloader):(下载一个response对象的)
发出从引擎获取的请求(Request),并等待请求的响应(Response),将其交还给引擎;6.中间件(Middleware):
中间件分为下载中间件(Download Middleware)和爬虫中间件(Spider Middleware),前者是在引擎和下载其中间用于加工请求requests和响应response的,后者则是位于爬虫和引擎之间,用于处理爬虫输入(response)和输出(items或requests)。可以通过插入自定义代码来扩展Scrapy功能。下载中间件(Download Middleware)
可以设置下载器中间件实现爬虫自动更换user-agent、IP等功能;
爬虫中间件(Spider Middleware)
2. 数据流(Data flow)
- 引擎打开一个网站(open a domain),找到处理该网站的Spider并向该spider请求start_url 第一个(批)要爬取的URL(s)
- 引擎从Spider中获取到第一个要爬取的URL并加入到调度器(Scheduler)作为请求以备调度
- 引擎向调度器请求下一个要爬取的URL
- 调度器返回下一个要爬取的URL给引擎,引擎将URL通过下载中间件并转发给下载器(Downloader)
- 一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过下载中间件发送给引擎
- 引擎从下载器中接收到Response,然后通过Spider中间件发送给Spider处理
- Spider处理Response并返回提取到的Item及(跟进的)新的Request给引擎
- 引擎将Spider返回的Item交给Item Pipeline,将Spider返回的Request交给调度器
- (从第二步)重复执行,直到调度器中没有待处理的request,引擎关闭
注意:只有当调度器中没有任何request了,整个程序才会停止执行。如果有下载失败的URL,会重新下载
3. 安装scrapy
安装wheel支持
$ pip install wheel
安装scrapy框架
$ pip install scrapy
window下,为了避免windows编译安装twisted依赖,安装下面的二进制包
$ pip install Twisted-18.4.0-cp35-cp35m-win_amd64.whl
windows下出现如下问题
copying src\twisted\words\xish\xpathparser.g -> build\lib.win-amd64-3.5\twisted\words\xish
running build_ext
building 'twisted.test.raiser' extension
error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build
Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools
解决方案是,下载编译好的twisted,https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
安装twisted
$pip install Twisted-18.4.0-cp 35-cp35m-win_amd 64.whl
之后在安装scrap y就没有什么问题
安装好,使用scrapy命令查看;
(blog) C:\Users\dell\PycharmProjects\spiders>scrapy
Scrapy 1.3.3 - no active project
Usage:
scrapy <command> [options] [args]
Available commands:
bench Run quick benchmark test
commands
fetch Fetch a URL using the Scrapy downloader
genspider Generate new spider using pre-defined templates
runspider Run a self-contained spider (without creating a project)
settings Get settings values
shell Interactive scraping console
startproject Create new project
version Print Scrapy version
view Open URL in browser, as seen by Scrapy
[ more ] More commands available when run from project directory
scrapy开发
项目编写流程
- 创建项目
使用 scrapy startproject proname 创建一个scrapy项目- 编写item
在items.py中编写Item类,明确从response中提取的item- 编写爬虫
编写spiders/proname_spider.py,即爬取网站的spider并提取出item- 编写item pipeline
item的处理,可以存储
1. 创建项目
豆瓣书评爬取
标签为"编程",第一页、第二页链接
随便找一个目录来创建项目,执行下面命令
$scrap y start project first
会产生如下目录和文件
first
├─ scrapy.cfg
└─ first
├─ items.py
├─ middlewares.py
├─ pipelines.py
├─ settings.py
├─ __init__.py
└─ spiders
└─ __init__.py
first:外部的first目录是整个项目目录,内部的first目录是整个项目的全局目录
scrapy.cfg:必须有的重要的项目的配置文件
first 项目目录(规定)
__init__.py 必须有,包文件
items.py 定义Item类,从scrapy.Item继承,里面定义scrapy.Field类实例
pipelines.py 重要的是process_item()方法,处理item
settings.py:
BOT_NAME 爬虫名
ROBOTSTXT_OBEY = True 是否遵从robots协议
USER_AGENT = '' 指定爬取时使用
CONCURRENT_REQEUST = 16 默认16个并行
DOWNLOAD_DELAY = 3 下载延时,一般要设置,不宜过快发起连续请求
COOKIES_ENABLED = False 缺省是启用,一般需要登录时才需要开启cookie
SPIDER_MIDDLEWARES 爬虫中间件
DOWNLOADER_MIDDLEWARES 下载中间件
'first.middlewares.FirstDownloaderMiddleware': 543
543 越小优先级越高
ITEM_PIPELINES 管道配置
'firstscrapy.pipelines.FirstscrapyPipeline': 300
item交给哪一个管道处理,300 越小优先级越高
spiders目录
__init__.py 必须有,可以在这里写爬虫类,也可以写爬虫子模块
# first/settings.py参考
BOT_NAME = 'first'
SPIDER_MODULES = ['first.spiders']
NEWSPIDER_MODULE = 'first.spiders'
USER_AGENT = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/55.0.2883.75 Safari/537.36"
ROBOTSTXT_OBEY = False
# Disable cookies (enabled by default)
COOKIES_ENABLED = False # 用户权限相关的时候
2. 编写Item
3. 编写爬虫
为爬取豆瓣书评编写爬虫类,在spiders目录下。
编写的爬虫类需要继承自scrapy.Spider,在这个类中定义爬虫名、爬取范围、其实地址等。
在scrapy.Spider中parse方法未实现,所以子类应该实现parse方法。该方法传入response对象。
# scrapy源码中 (创建 bookspider后可以之间删除,不用自己写)
class Spider():
def parse(self, response):
raise NotImplementedError
爬取读书频道,tag为“编程”的书名和评分。
https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=20&type=T
# genspider -t 指定使用crawl模板
scrapy genspider -t crawl wxapp_spider
使用模板创建spider-basic
(blog) F:\Projects\scrapy>scrapy genspider -t basic book douban.com
Created spider 'book' using template 'basic' in module:
first.spiders.book
# book.py
import scrapy
from scrapy.http.response.html import HtmlResponse
class BookSpider(scrapy.Spider):
name = "book"
allowed_domains = ["douban.com"]
start_urls = ['https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=20&type=T']
# https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=980&type=T
def parse(self, response:HtmlResponse):
print(type(response))
titles = response.xpath('//li[@class="subject-item"]//h2/a//text()')
for title in titles:
print(title)
#-----------------------------------------------------------------------------------------------
<Selector xpath='//li[@class="subject-item"]//h2/a//text()' data='\n\n 深入理解计算机系统\n\n\n \n\n '>
<Selector xpath='//li[@class="subject-item"]//h2/a//text()' data='\n\n Head First HTML与CSS(第2版)\n\n\n \n\n '>
<Selector xpath='//li[@class="subject-item"]//h2/a//text()' data='\n\n 程序员面试金典(第6版)\n\n\n \n\n '>
<Selector xpath='//li[@class="subject-item"]//h2/a//text()' data='\n\n 利用Python进行数据分析 原书第2版\n\n\n \n\n '>
使用crawl爬取子命令
$scrap y list
$scrap y crawl-h
指定爬虫名称开始爬取;
$scrap crawl book
# 不打印日志;
(blog) F:\Projects\scrapy>scrapy crawl book --nolog
<class 'scrapy.http.response.html.HtmlResponse'>
response是服务器端HTTP响应,它是scrapy.http.response.html.HtmlResponse类。由此,修改代码如下
Xpath + CSS 选择器与 scrapy 最核心组件的使用
# items.py
import scrapy
class FirstItem(scrapy.Item):
title = scrapy.Field()
rate = scrapy.Field()
# book.py
# -*- coding: utf-8 -*-
import scrapy
from scrapy.http.response.html import HtmlResponse
from ..items import FirstItem
class BookSpider(scrapy.Spider):
name = "book"
allowed_domains = ["douban.com"]
start_urls = ['https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=20&type=T']
# https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=980&type=T
def parse(self, response:HtmlResponse):
# items = []
titles = response.xpath('//li[@class="subject-item"]')
for t in titles:
title = (t.css('h2 a::text').extract()[0].strip())
rate = t.xpath('.//span[@class="rating_nums"]/text()').extract_first()
item = FirstItem() # 类对象;
item['title'] = title
item['rate'] = rate
yield item
# items.append(item)
# return items
#-------------------------------------
{'rate': '9.5', 'title': '深入理解计算机系统'}
(blog) F:\Projects\scrapy>scrapy crawl book -o books.json
#--------------------------------------------------------------------------------------------------------
# unicode编码;
{"title": "Java\u7f16\u7a0b\u601d\u60f3 \uff08\u7b2c4\u7248\uff09", "rate": "9.1"},
{"title": "\u6df1\u5165\u7406\u89e3\u8ba1\u7b97\u673a\u7cfb\u7edf\uff08\u539f\u4e66\u7b2c3\u7248\uff09", "rate": "9.8"},
{"title": "\u7a0b\u5e8f\u5458\u4fee\u70bc\u4e4b\u9053", "rate": "8.6"},
3.1 解析HTML
爬虫获得的内容response对象,可以使用解析库来解析。
scrapy包装了lxml,父类TextResponse类也提供了xpath方法和css方法,可以混合使用这两套接口解析HTML。
选择器 参考 https://scrapy-chs.readthedocs.io/zh_CN/0.24/topics/selectors.html#id3
extract_first() / extract()[0]
# 下载 html
import scrapy
from scrapy.http.response.html import HtmlResponse
from ..items import FirstItem
class BookSpider(scrapy.Spider):
name = "book"
allowed_domains = ["douban.com"]
start_urls = ['https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=20&type=T']
# https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=980&type=T
def parse(self, response:HtmlResponse):
print(type(response))
with open('./douban.html', 'w', encoding=response.encoding) as f:
f.write(response.text)
# ----------------------------------------
import scrapy
from scrapy.http.response.html import HtmlResponse
from scrapy.selector.unified import SelectorList, Selector
response = HtmlResponse('file:///F:\Projects\scrapy\douban.html', encoding='utf-8') # 构造对象;
with open('F:\Projects\scrapy\douban.html', 'rb') as f: # 二进制的数据读取;和编码无关;
data = f.read()
response._set_body(data)
print('----------------------------')
# xpath 解析 ;
subjects = response.xpath('//li[@class="subject-item"]')
# print(subjects, type(subjects)) # selecter list
for subject in subjects:
title = "".join((x.strip() for x in subject.xpath('.//h2/a//text()').extract()))
rate = subject.css('span.rating_nums').xpath('./text()').extract_first() # extract_first()/extract()[0]
print(title,rate)
print('-' * 30)
# css解析 : 页码
#------------------------------------------------------------
Python编程: 从入门到实践 9.1
黑客与画家: 硅谷创业之父Paul Graham文集 8.7
编码: 隐匿在计算机软硬件背后的语言 9.2
计算机程序的构造和解释(原书第2版): 原书第2版 9.5
代码大全(第2版) 9.3
Python编程快速上手: 让繁琐工作自动化 8.9
深入理解计算机系统(原书第2版) 9.7
提取页码
调度器自带去重功能;
拿到的URL有相对和绝对之分; /tag/编程?start=20&type=T - / 为根开始
import scrapy
from scrapy.http.response.html import HtmlResponse
from scrapy.selector.unified import SelectorList, Selector
response = HtmlResponse('file:///F:\Projects\scrapy\douban.html', encoding='utf-8') # 构造对象;
with open('F:\Projects\scrapy\douban.html', 'rb') as f: # 二进制的数据读取;和编码无关;
data = f.read()
response._set_body(data)
print('----------------------------')
# xpath 解析 ;
subjects = response.xpath('//li[@class="subject-item"]')
# print(subjects, type(subjects)) # selecter list
for subject in subjects:
title = "".join((x.strip() for x in subject.xpath('.//h2/a//text()').extract()))
rate = subject.css('span.rating_nums').xpath('./text()').extract_first() # extract_first()/extract()[0]
# print(title,rate)
print('-' * 30)
# css解析 : 页码
next_url = response.xpath('//div[@class="paginator"]/span[@class="next"]/a/@href') # /tag/编程?start=20&type=T
print(response.urljoin(next_url)) # 拼凑 URL ;
import scrapy,re
from scrapy.http.response.html import HtmlResponse
from scrapy.selector.unified import SelectorList, Selector
response = HtmlResponse('file:///F:\Projects\scrapy\douban.html', encoding='utf-8') # 构造对象;
with open('F:\Projects\scrapy\douban.html', 'rb') as f: # 二进制的数据读取;和编码无关;
data = f.read()
response._set_body(data)
print('----------------------------')
# css解析 : 页码
# next_urls = response.xpath('//div[@class="paginator"]/a/@href').extract() # selector_list /tag/编程?start=20&type=T
# next_urls = response.xpath('//div[@class="paginator"]/a/@href').re(r'.*start=\d+.*') # re 的贪婪模式;得到list
# next_urls = response.xpath('//div[@class="paginator"]/a/@href').re(r'.*start=[24]\d+.*') # 指定页 20 40
# next_urls = response.xpath('//div[@class="paginator"]/a[text()<4]/@href').extract()
print(next_urls)
scrapy 中 爬取数据;
3.2 item封装数据
4. pipeline处理
将book spider.py中Book Spider改成生成器, 只需要把return items改造成yield item, 即由产生一个列表变成yield一个个item。
脚手架帮我们创建了一个pipelines.py文件和一个类。
4.1开启pipeline
整数300表示优先级,越小越高。取值范围为0-1000。
# Configure item pipelines
# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'first.pipelines.FirstPipeline': 300,
}
整数300表示优先级,越小越高。取值范围为0-1000.
4.2 常用方法
名称 | 参数 | |
---|---|---|
process_item(self, item, spider) | item爬取的一个个数据 spider表示item的爬取者 每一个item处理都调用 返回一个Item对象,或抛出DropItem异常 被丢弃的Item对象将不会被之后的pipeline组件处理 |
必须 |
open_spider(self, spider) | spider表示被开启的spider 调用一次 |
可选 |
close_spider(self, spider) | spider表示被关闭的spider 调用一次 |
可选 |
_init_(self) | spider实例创建时 调用一次 |
可选 |
常用方法
class FirstPipeline(object):
def __init__(self): # 实例化过程, 可以不用
print('~~~~~init~~~~~')
def open_spider(self, spider):
self.count = 0
def process_item(self, item, spider):
print(item, '--------------')
return item
def close_spider(self, spider):
print('=========={}'.format(self.count))
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html
import simplejson
class FirstPipeline(object):
def __init__(self): # 实例化过程, 可以不用
print('~~~~~init~~~~~')
def open_spider(self, spider):
self.count = 0
self.file = open('./book1.json', 'w', encoding='utf-8')
self.file.write('[\n')
self.file.flush()
def process_item(self, item, spider):
print(item, '--------------')
self.count += 1
self.file.write(simplejson.dumps(dict(item)) + ',\n') # dict
return item
def close_spider(self, spider):
print('=========={}'.format(self.count))
self.file.write(']')
self.file.close()
需求 : 通过pipeline将爬取的数据存入json文件中
import scrapy
from scrapy.http.response.html import HtmlResponse
from ..items import FirstItem
from scrapy.http.request import Request
class BookSpider(scrapy.Spider):
name = "book" # 爬虫名称
allowed_domains = ["douban.com"] # 爬虫爬取范围
start_urls = ['https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=0&type=T']
# https://book.douban.com/tag/%E7%BC%96%E7%A8%8B?start=980&type=T
def parse(self, response:HtmlResponse):
print(type(response))
print(response.url)
next_urls = response.xpath('//div[@class="paginator"]/a[text()<3]/@href').extract()
# print(next_urls, '++++++++++++++++')
yield from (Request(response.urljoin(url)) for url in next_urls)
subjects = response.xpath('//li[@class="subject-item"]')
for subject in subjects:
title = "".join((x.strip() for x in subject.xpath('.//h2/a//text()').extract()))
rate = subject.css('span.rating_nums').xpath('./text()').extract_first() # extract_first()/extract()[0]
item = FirstItem()
item['title'] = title
item['rate'] = rate
print(item)
yield item
#--------------------------------------------------books1.json
{"title": "JavaScript\u9ad8\u7ea7\u7a0b\u5e8f\u8bbe\u8ba1\uff08\u7b2c3\u7248\uff09", "rate": "9.3"},
{"title": "Python\u6df1\u5ea6\u5b66\u4e60", "rate": "9.6"},
{"title": "C++ Primer \u4e2d\u6587\u7248\uff08\u7b2c 5 \u7248\uff09", "rate": "9.4"},
{"title": "\u7b97\u6cd5\uff08\u7b2c4\u7248\uff09", "rate": "9.4"},
]
多了一个 ,; 手动删掉就行,也可以做判断删除;