66.1-scrapy框架概述和编程流程

2020-09-07  本文已影响0人  BeautifulSoulpy
负担,挑得起就是礼物,挑不起就是包袱;压力,撑得住就是成长,撑不住就是苦难。你唯一应该超越的人就是昨天的你 !

总结:

  1. CSS不用写当前(//); Xpath要写当前(./);
  2. extract_first() / extract()[0] ; 提取data 结果 ;
  3. 调度器(Scheduler)中自带去重功能;
  4. 拿到的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)

  1. 引擎打开一个网站(open a domain),找到处理该网站的Spider并向该spider请求start_url 第一个(批)要爬取的URL(s)
  2. 引擎从Spider中获取到第一个要爬取的URL并加入到调度器(Scheduler)作为请求以备调度
  3. 引擎向调度器请求下一个要爬取的URL
  4. 调度器返回下一个要爬取的URL给引擎,引擎将URL通过下载中间件并转发给下载器(Downloader)
  5. 一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过下载中间件发送给引擎
  6. 引擎从下载器中接收到Response,然后通过Spider中间件发送给Spider处理
  7. Spider处理Response并返回提取到的Item及(跟进的)新的Request给引擎
  8. 引擎将Spider返回的Item交给Item Pipeline,将Spider返回的Request交给调度器
  9. (从第二步)重复执行,直到调度器中没有待处理的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开发

项目编写流程

  1. 创建项目
    使用 scrapy startproject proname 创建一个scrapy项目
  2. 编写item
    在items.py中编写Item类,明确从response中提取的item
  3. 编写爬虫
    编写spiders/proname_spider.py,即爬取网站的spider并提取出item
  4. 编写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"},
]

多了一个 ,; 手动删掉就行,也可以做判断删除;

上一篇 下一篇

猜你喜欢

热点阅读