爬虫教程( 2 ) --- scrapy 教程、实战

​scrapy 英文文档:https://docs.scrapy.org/en/latest/index.html
scrapy 中文文档:https://www.osgeo.cn/scrapy/index.html
参考:https://piaosanlang.gitbooks.io/spiders/content/

1、爬虫框架

github 爬虫 框架

  • :https://github.com/search?q=爬虫+框架
  • :https://github.com/search?q=spider

常用爬虫框架

爬虫框架中比较好用的是 Scrapy 和 PySpider。

PySpider

文档:https://docs.pyspider.org/en/latest/Quickstart/

github:https://github.com/binux/pyspider

优点:分布式框架,上手更简单,操作更加简便,因为它增加了 WEB 界面,写爬虫迅速,集成了phantomjs,可以用来抓取js渲染的页面。
缺点:自定义程度低

Scrapy

文档:https://docs.scrapy.org/en/latest/

中文文档 ( 版本比较旧 ) :https://www.osgeo.cn/scrapy/intro/overview.html

优点:自定义程度高,比 PySpider 更底层一些,适合学习研究,需要学习的相关知识多,拿来研究分布式和多线程等等是最合适不过的。

缺点:非分布式框架(可以用 scrapy-redis 分布式框架)

Pycharm 中调试 scrapy 爬虫的两种方法

通常,运行scrapy爬虫的方式是在命令行输入scrapy crawl ,调试的常用方式是在命令行输入scrapy shell 。总的来说,调试方法比较单一。其实,还有两种调试方法,可以在pycharm中实现调试。

使用 scrapy.cmdline 的 execute 方法

首先,在项目文件 scrapy.cfg 的同级建立 main.py 文件(注意,必须是同级建立),在其中键入如下代码:

from scrapy import cmdline 
import sys
import os

sys.path.append(os.path.dirname(os.path.abspath(__file__)))

# 你需要将此处的spider_name替换为你自己的爬虫名称
cmdline.execute(['scrapy', 'crawl', 'spider_name'])  

# 或者 cmdline.execute('scrapy crawl spider_name'.split(' '))

pass

在其余爬虫文件中设置断点后,运行main.py,即可实现在pycharm中的调试。

使用 scrapy 的 CrawlerProcess 方法

在项目文件scrapy.cfg的同级建立main.py文件(注意,必须是同级建立),在其中键入如下代码:

from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings

if __name__ == '__main__':
    process = CrawlerProcess(get_project_settings())
    #  你需要将此处的spider_name替换为你自己的爬虫名称
    process.crawl('spider_name')
    process.start()

在其余爬虫文件中设置断点后,运行main.py,即可实现在pycharm中的调试。

两种方式都很简单实用,值得掌握。

可视化 爬虫 框架

Selenium 

selenium 在爬虫中的应用

  • 获取动态网页中的数据,一些动态的数据我们在获取的源码中并没有显示的之一类动态加载数据
  • 可用于模拟登录

安装:pip install selenium

驱动下载:http://chromedriver.storage.googleapis.com/index.html

Playwright

新一代爬虫利器 -- Playwright:https://zhuanlan.zhihu.com/p/499597451

github:https://github.com/microsoft/playwright

Playwright for TypeScript, JavaScript, Python, .NET, or Java

Playwright (剧作家) 是专门为满足端到端测试的需求而创建的。Playwright支持所有现代渲染引擎,包括Chromium,WebKit和Firefox。在Windows,Linux和macOS上进行测试,本地或CI,无头或以Google Chrome for Android和Mobile Safari的本机移动仿真为标题。

它可以通过单个API自动执行 Chromium,Firefox 和 WebKit 浏览器,连代码都不用写,就能实现自动化功能。虽然测试工具 selenium 具有完备的文档,但是其学习成本让一众小白们望而却步,对比之下 playwright-python 简直是小白们的神器。

提示:playwright 还可支持移动端的浏览器模拟。

ichrome

github:https://github.com/ClericPy/ichrome

基于 Chrome Devtools Protocol(CDP) 和 python3.7+ 来人为的控制 Chrome

pychrome 也是 Google Chrome Dev Protocol [threading base] 的一个 Python 包

2、Scrapy 官方文档

scrapy-cookbook ( 中文版 ):https://scrapy-cookbook.readthedocs.io/zh_CN/latest/index.html

Scrapy 官网文档 ( 英文版 ):https://docs.scrapy.org/en/latest/index.html

Scrapy 是一个快速的高级网络抓取和网络抓取框架,用于抓取网站并从其页面中提取结构化数据。它可以用于广泛的目的,从数据挖掘到监控和自动化测试。

2.1 入门

Scrapy 一瞥

Scrapy at a glance

安装指南

Installation guide

Scrapy 教程

Scrapy Tutorial

Scrapy 一些示例

Examples

2.2 基本概念

基本概念

  • Command line tool
  • Spiders
  • Selectors
  • Items
  • Item Loaders
  • Scrapy shell
  • Item Pipeline
  • Feed exports
  • Requests and Responses
  • Link Extractors
  • Settings
  • Exceptions

命令行工具

Spiders

Selectors

Items

Item Loaders

Scrapy shell

Item Pipeline 

Feed exports ( 命令行 导出 数据 )

Requests 和 Responses ( 请求、响应 )

Link Extractors ( 链接提取 )

Settings ( 设置 )

Exceptions ( 异常 )

2.3 内置服务

内置服务

  • Logging
  • Stats Collection
  • Sending e-mail
  • Telnet Console

日志

状态统计信息

发送 Email

telnet 控制

2.4 一些常见问题

一些常见问题

  • Frequently Asked Questions
  • Debugging Spiders
  • Spiders Contracts
  • Common Practices
  • Broad Crawls
  • Using your browser’s Developer Tools for scraping
  • Selecting dynamically-loaded content
  • Debugging memory leaks
  • Downloading and processing files and images
  • Deploying Spiders
  • AutoThrottle extension
  • Benchmarking
  • Jobs: pausing and resuming crawls
  • Coroutines
  • asyncio

经常被问的问题

调试 脚本

Spider 约定

一些通用用法

大规模抓取

使用浏览器的开发人员工具进行抓取

选择动态加载的内容

调试内存泄漏

下载和处理文件和图像

部署 Spider

自动负载限制爬网速度

基准测试

暂停和恢复爬行

协程

asyncio ( 异步 )

2.5 Scrapy 扩展知识

Scrapy 扩展

  • Architecture overview
  • Downloader Middleware
  • Spider Middleware
  • Extensions
  • Signals
  • Scheduler
  • Item Exporters
  • Components
  • Core API

体系结构概述

下载器 中间件

Spider 中间件

扩展

信号

调度器

Item Exporters

组件

核心API

2、Scrapy 介绍

Scrapy 官方架构图

Scrapy 官方文档:https://docs.scrapy.org/en/latest/

下图显示了 Scrapy 体系结构及其概述 系统内发生的数据流的组件和概述 (由红色箭头显示)。包括组件的简要说明 下面包含有关它们的更多详细信息的链接。

处理流程图:

爬虫教程( 2 ) --- scrapy 教程、实战_第1张图片

Scrapy 中的数据流由执行引擎控制,流程如下:

  1. 引擎 首先从自己编写的 spider 中读取起始 url,然后封装成 Request对象
  2. 引擎 把 "封装后的Request对象" 传递给 调度器 ( 调度器主要作用就是管理、调度url,可以简单的看作是一个 "间接的队列",对 Requestd对象 管理、过滤 等操作)。
  3. 引擎 请求 调度器,调度器返回一个 Request对象 给引擎。
  4. 引擎 将 Request对象 发送到下载器。下载器会将请求通过下载器中间件。( process_request() )
  5. 下载器完成页面下载后,下载器会将生成的 响应Response 通过下载器中间件(process_response()),最后将其发送到引擎。
  6. 引擎接收来自下载器的 响应 并将其发送给 自己编写的 spider 进行处理,但是在发送之前会先 传递 通过spider中间件(参见process_spider_input())。
  7. 自己编写的 spider 处理响应并返回 "抓取的数据Item" 及(跟进的)新的Request给引擎,通过蜘蛛中间件(参见process_spider_output())。
  8. 引擎将 "抓取的数据Item" 发送到 pipeline,将 "新的请求" 发送到 调度程序。并继续从调度器中获取 下一个 "Request对象" 来抓取。
  9. 该过程重复(从步骤 3 开始),直到没有更多地 request对象 ,最后关闭引擎。

整个工作流程

  • 1.引擎 将爬虫中起始的url构造成request对象,并传递给调度器。
  • 2.引擎 从 调度器 中获取到request对象然后交给下载器。
  • 3.由 下载器 来获取到页面源代码,并封装成response对象,并返回给引擎。
  • 4.引擎 将获取到的response对象传递给 spider,由 spider 对数据进行解析(parse),并返回给引擎
  • 5.引擎将数据传递给 pipeline 进行数据持久化保存或进一步的数据处理
  • 6.在此期间如果spider中提取到的并不是数据。而是子页面ur.可以进一步提交给调度器,进而重复 步骤2 的过程

Scrapy 主要组件:

五个功能模块

  • 引擎(Scrapy):用来处理整个系统的数据流处理。控制各个模块之间的通信
  • 调度器(Scheduler): 负责引擎发过来的请求,从 待下载链接 中取出一个链接(URL)并压入队列同时去除重复的网址,决定下一个要抓取的网址是什么 。启动采集模块,即 Spiders模块。
  • 下载器(Downloader): 用于下载网页内容, 并将下载的网页内容返回给引擎Scrapy
  • 爬虫(Spiders): 爬虫是主要干活的,解析并提取 下载器获取的网内容,用户也可以从中提取出链接,让Scrapy继续抓取下一个页面。提取的 内容就是 item,item 会传递给 pipeline 处理。
  • 项目管道(item Pipeline): 负责处理爬虫从网页中抽取 item,主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。当页面被爬虫解析后,将被发送到项目管道,并经过几个特定的次序处理数据。

三大中间件

  • 下载器中间件(Downloader Middlewares): 位于Scrapy引擎和下载器之间的框架,主要是处理Scrapy引擎与下载器之间的请求及响应。
  • 爬虫中间件(Spider Middlewares): 介于 Scrapy引擎和爬虫之间的框架,主要工作是处理蜘蛛的响应输入和请求输出。
  • 调度中间件(Scheduler Middewares): 介于 Scrapy引擎和调度之间的中间件,从Scrapy引擎发送到调度的请求和响应。

Scrapy 安装

Scrapy 是用 Python 开发的一个快速、高层次的 web 抓取框架;Scrapy 用途广泛,可以用于数据挖掘、监测和自动化测试。Scrapy 使用了 Twisted 异步网络库来处理网络通讯。

windows 安装:pip install scrapy
Linux 安装:sudo pip install Scrapy

验证安装:输入 Scrapy 或者 scrapy(大小写都可以)。如果提示如下命令,就证明安装成功。

注意:linux依然严格区分大小写

3、Scrapy 入门教程

大致流程:

  1. 创建一个 Scrapy 项目
  2. 定义提取的结构化数据 (Item)
  3. 编写爬取网站的 spider 并提取出结构化数据 (Item)
  4. 编写 Item Pipeline 来存储提取到的 Item (即结构化数据)
  5. 配置 setting.py

在运行 crawl 时添加 -a 可以传递 Spider 参数,spider 参数一般用来定义初始URL或者指定限制爬取网站的部分。 您也可以使用其来配置spider的任何功能。

scrapy crawl myspider -a category=electronics

Spider 在构造器 (constructor) 中获取参数:

import scrapy

class MySpider(Spider):
    name = 'myspider'

    def __init__(self, category=None, *args, **kwargs):
        super(MySpider, self).__init__(*args, **kwargs)
        self.start_urls = ['http://www.example.com/categories/%s' % category]

脚本运行 Scrapy:https://docs.scrapy.org/en/latest/topics/practices.html

创建一个 Scrapy 项目

在开始爬取之前必须创建一个新的 Scrapy 项目。 进入打算存储代码的目录中。运行下列命令: scrapy startproject tutorial

爬虫教程( 2 ) --- scrapy 教程、实战_第2张图片

该命令将会创建包含下列内容的 tutorial 目录,这些文件分别是:

scrapy.cfg: 项目的配置文件;(用于发布到服务器)
tutorial/: 该项目文件夹。之后将在此编写Python代码。
tutorial/items.py: 项目中的item文件;(定义结构化数据字段field).
tutorial/pipelines.py: 项目中的pipelines文件;(用于存放执行后期数据处理的功能,定义如何存储结构化数据)
tutorial/settings.py: 项目的设置文件;(如何修改User-Agent,设置爬取时间间隔,设置代理,配置中间件等等)
tutorial/spiders/: 放置spider代码的目录;(编写爬取网站规则)

windows 下创建:

爬虫教程( 2 ) --- scrapy 教程、实战_第3张图片

Pycharm 打开 Scrapy 工程:

爬虫教程( 2 ) --- scrapy 教程、实战_第4张图片

定义 Item

Item 定义结构化数据字段,用来保存爬取到的数据;其使用方法和python字典类似。可以通过创建一个 scrapy.Item 类, 并且定义类型为 scrapy.Field 的类属性来定义一个 Item。

示例:腾讯招聘中获取 职位名称、职位详情页url、职位类别、人数、工作地点以及发布时间。 对此,在item中定义相应的字段。编辑 tutorial 目录中的 items.py 文件:

import scrapy

class RecruitItem(scrapy.Item):
    name  = scrapy.Field()
    detailLink = scrapy.Field()
    catalog = scrapy.Field()
    recruitNumber = scrapy.Field()
    workLocation = scrapy.Field()
    publishTime = scrapy.Field()

编写 爬虫 (Spider) 文件

创建一个 Spider,必须继承 'scrapy.Spider' 类, 需要定义以下三个属性:

  • name:  spider 名字;必须是唯一的
  • start_urls: 初始的 URL 列表
  • parse(self, response):每个初始 URL 完成下载后被调用。这个函数要完成的功能:
            1. 负责解析返回的网页数据(response.body),提取结构化数据(生成item)
            2. 生成需要下一页的请求 URL。

示例 Spider 代码,保存在 tutorial/spiders 目录下的 tencent_spider.py 文件中:

import scrapy

class RecruitSpider(scrapy.spiders.Spider):
    name = "tencent"
    allowed_domains = ["hr.tencent.com"]
    start_urls = [
        "http://hr.tencent.com/position.php?&start=0#a"
    ]

    def parse(self, response):
        f = open('tengxun.txt', 'wb')
        f.write(response.body)
        f.close()

运行 编写的爬虫

进入项目的根目录,执行下列命令启动 spider:

scrapy crawl tencent

crawl tencent 启动用于爬取 tencent 的 spider,您将得到类似的输出:

爬虫教程( 2 ) --- scrapy 教程、实战_第5张图片

现在,查看当前目录,会注意到有文件被创建了: tengxun.txt。

刚才发生了什么?

  • Scrapy 为 Spider 的 start_urls 属性中的每个 URL 创建了 scrapy.Request 对象,并将 parse 方法作为回调函数(callback)赋值给了 Request。
  • Request 对象经过调度,执行生成 scrapy.http.Response 对象并送回给 parse() 方法。

提取 Item ( xpath、CSS、re )

Scrapy 内置的 Selectors 模块提供了对  XPath 和 CSS Selector 的支持。也可以单独拿出来使用

单独使用 示例:

from scrapy import Selector


temp_string = '''

  
    Harry Potter
    29.99
  
  
    Learning XML
    39.95
  

'''


if __name__ == '__main__':
    s = Selector(text=temp_string)
    print(s.xpath('//book[1]/title/text()').extract_first())
    print(s.xpath('//book[1]/price/text()').extract_first())
    pass

XPath 表达式的例子及对应的含义:

/html/head/title           选择文档中  标签内的  元素
/html/head/title/text()    选择上面提到的 <title> 元素的文字
//td                       选择所有的 <td> 元素
//div[@class="mine"]       选择所有具有 class="mine" 属性的 div 元素</code></pre> 
  <p>Selector 有四个基本的方法:</p> 
  <pre class="has"><code class="language-plain">xpath()       传入xpath表达式,返回该表达式所对应的所有节点的selector list列表 。
css()         传入CSS表达式,返回该表达式所对应的所有节点的selector list列表.
extract()     序列化该节点为unicode字符串并返回list。
re()          根据传入的正则表达式对数据进行提取,返回unicode字符串list列表。</code></pre> 
  <p></p> 
  <h3>scrapy shell</h3> 
  <p>尝试 Selector 选择器</p> 
  <p>为了介绍 Selector的使用方法,接下来我们将要使用内置的 scrapy shell 。Scrapy Shell 需要您预装好 IPython (一个扩展的Python终端)。您需要进入项目的根目录,执行下列命令来启动 shell: </p> 
  <pre class="has"><code class="language-plain">scrapy shell "http://hr.tencent.com/position.php?&start=0#a"</code></pre> 
  <p>注解: 当您在终端运行 Scrapy 时,请一定记得给 url 地址加上引号,否则包含参数的 url (例如 & 字符)会导致 Scrapy 运行失败。<br> shell 的输出类似:</p> 
  <p style="text-align:center;"><a href="http://img.e-com-net.com/image/info8/94d3690b83a04260b18339fc9471c4de.gif" target="_blank"><img alt="爬虫教程( 2 ) --- scrapy 教程、实战_第6张图片" class="has" height="523" src="http://img.e-com-net.com/image/info8/94d3690b83a04260b18339fc9471c4de.gif" width="783" style="border:1px solid black;"></a></p> 
  <p>当 shell 载入后,将得到一个包含 response 数据的本地 response 变量。</p> 
  <p>输入 response.body 将输出 response 的包体, 输出 response.headers 可以看到 response 的包头。 </p> 
  <p>当输入 response.selector 时, 将获取到一个response 初始化的类 Selector 的对象。</p> 
  <p>此时,可以通过使用 response.selector.xpath() 或 response.selector.css() 来对 response 进行查询。</p> 
  <p>scrapy 对 response.selector.xpath() 及 response.selector.css() 提供了一些快捷方式,例如 response.xpath() 或 response.css()</p> 
  <pre class="has"><code class="language-python">response.xpath('//title')
[<Selector xpath='//title' data=u'<title>\u804c\u4f4d\u641c\u7d22 | \u793e\u4f1a\u62db\u8058 | Tencent \u817e\u8baf\u62db\u8058</title'>]

response.xpath('//title').extract()
[u'<title>\u804c\u4f4d\u641c\u7d22 | \u793e\u4f1a\u62db\u8058 | Tencent \u817e\u8baf\u62db\u8058']

print response.xpath('//title').extract()[0]
职位搜索 | 社会招聘 | Tencent 腾讯招聘

response.xpath('//title/text()')


response.xpath('//title/text()')[0].extract()
u'\u804c\u4f4d\u641c\u7d22 | \u793e\u4f1a\u62db\u8058 | Tencent \u817e\u8baf\u62db\u8058'

print response.xpath('//title/text()')[0].extract()
职位搜索 | 社会招聘 | Tencent 腾讯招聘

response.xpath('//title/text()').re('(\w+):')
[u'\u804c\u4f4d\u641c\u7d22',
 u'\u793e\u4f1a\u62db\u8058',
 u'Tencent',
 u'\u817e\u8baf\u62db\u8058']

提取数据

现在,从页面中提取些有用的数据。

# 通过 XPath 选择该页面中网站列表里所有 lass=even 元素
site = response.xpath('//*[@class="even"]')

# 职位名称:
print site[0].xpath('./td[1]/a/text()').extract()[0]
# TEG15-运营开发工程师(深圳)

# 职位名称详情页:
print site[0].xpath('./td[1]/a/@href').extract()[0]
position_detail.php?id=20744&keywords=&tid=0&lid=0

# 职位类别:
print site[0].xpath('./td[2]/text()').extract()[0]
# 技术类

对于 .xpath() 调用返回 selector 组成的 list, 因此可以拼接更多的 .xpath() 来进一步获取某个节点。

for sel in response.xpath('//*[@class="even"]'):
    name = sel.xpath('./td[1]/a/text()').extract()[0]
    detailLink = sel.xpath('./td[1]/a/@href').extract()[0]
    catalog = sel.xpath('./td[2]/text()').extract()[0]
    recruitNumber = sel.xpath('./td[3]/text()').extract()[0]
    workLocation = sel.xpath('./td[4]/text()').extract()[0]
    publishTime = sel.xpath('./td[5]/text()').extract()[0]
    print name, detailLink, catalog,recruitNumber,workLocation,publishTime

在我们的 tencent_spider.py 文件修改成如下代码:

import scrapy

class RecruitSpider(scrapy.spiders.Spider):
    name = "tencent"
    allowed_domains = ["hr.tencent.com"]
    start_urls = [
        "http://hr.tencent.com/position.php?&start=0#a"
    ]

    def parse(self, response):
        for sel in response.xpath('//*[@class="even"]'):
            name = sel.xpath('./td[1]/a/text()').extract()[0]
            detailLink = sel.xpath('./td[1]/a/@href').extract()[0]
            catalog = sel.xpath('./td[2]/text()').extract()[0]
            recruitNumber = sel.xpath('./td[3]/text()').extract()[0]
            workLocation = sel.xpath('./td[4]/text()').extract()[0]
            publishTime = sel.xpath('./td[5]/text()').extract()[0]
            print name, detailLink, catalog,recruitNumber,workLocation,publishTime

如图所示:

爬虫教程( 2 ) --- scrapy 教程、实战_第7张图片

现在尝试再次爬取 hr.tencent.com,您将看到爬取到的网站信息被成功输出:

scrapy crawl tencent

运行过程:

爬虫教程( 2 ) --- scrapy 教程、实战_第8张图片

编写 item

Item 对象是自定义的 python 字典。可以使用标准的字典语法来获取到其每个字段的值。输入 `scrapy shell'

import scrapy

class RecruitItem(scrapy.Item):
    name  = scrapy.Field()
    detailLink = scrapy.Field()
    catalog = scrapy.Field()
    recruitNumber = scrapy.Field()
    workLocation = scrapy.Field()
    publishTime = scrapy.Field()

item = RecruitItem()
item['name'] = 'sanlang'
item['name']
'sanlang'

一般来说,Spider 将会将爬取到的数据以 Item 对象返回。所以为了将爬取的数据返回,最终 tencent_spider.py 代码将是:

import scrapy
from tutorial.items import RecruitItem
class RecruitSpider(scrapy.spiders.Spider):
    name = "tencent"
    allowed_domains = ["hr.tencent.com"]
    start_urls = [
        "http://hr.tencent.com/position.php?&start=0#a"
    ]

    def parse(self, response):
        for sel in response.xpath('//*[@class="even"]'):
            name = sel.xpath('./td[1]/a/text()').extract()[0]
            detailLink = sel.xpath('./td[1]/a/@href').extract()[0]
            catalog = sel.xpath('./td[2]/text()').extract()[0]
            recruitNumber = sel.xpath('./td[3]/text()').extract()[0]
            workLocation = sel.xpath('./td[4]/text()').extract()[0]
            publishTime = sel.xpath('./td[5]/text()').extract()[0]
            print name, detailLink, catalog,recruitNumber,workLocation,publishTime
            item = RecruitItem()
            item['name']=name.encode('utf-8')
            item['detailLink']=detailLink.encode('utf-8')
            item['catalog']=catalog.encode('utf-8')
            item['recruitNumber']=recruitNumber.encode('utf-8')
            item['workLocation']=workLocation.encode('utf-8')
            item['publishTime']=publishTime.encode('utf-8')

            yield item

现在对 hr.tencent.com 进行爬取将会产生 RecruitItem 对象:

爬虫教程( 2 ) --- scrapy 教程、实战_第9张图片

命令行 保存 抓取的数据

最简单存储爬取的数据的方式是使用 Feed exports:

scrapy crawl tencent -o items.json

该命令将采用 JSON 格式对爬取的数据进行序列化,生成 items.json 文件。

如果需要对爬取到的item做更多更为复杂的操作,您可以编写 Item Pipeline 。 类似于我们在创建项目时对Item做的,用于您编写自己的 tutorial/pipelines.py 也被创建。 不过如果您仅仅想要保存item,您不需要实现任何的pipeline。

编写 Pipelines

item pipiline 组件是一个独立的 Python 类,必须实现 process_item 方法:

  • process_item(self, item, spider):当 Item 在 Spider 中被收集之后,都需要调用该方法。参数:  item - 爬取的结构化数据。 spider – 爬取该 item 的 spider
  • open_spider(self, spider):当 spider 被开启时,这个方法被调用。参数:spider   – 被开启的spider
  • close_spider(spider):当 spider 被关闭时,这个方法被调用。参数:spider – 被关闭的spider

将 item 写入 JSON 文件

以下 pipeline 将所有爬取到的 item,存储到一个独立地 items.json 文件,每行包含一个序列化为 'JSON' 格式的 'item':

import json

class JsonWriterPipeline(object):

    def __init__(self):
        self.file = open('items.json', 'wb')

    def process_item(self, item, spider):
        line = json.dumps(dict(item),ensure_ascii=False) + "\n"
        self.file.write(line)
        return item

启用一个Item Pipeline组件

为了启用 Item Pipeline 组件,必须将它的类添加到 settings.py 文件 ITEM_PIPELINES 配置,就像下面这个例子:

ITEM_PIPELINES = {
    #'tutorial.pipelines.PricePipeline': 300,
    'tutorial.pipelines.JsonWriterPipeline': 800,
}

分配给每个类的整型值,确定了他们运行的顺序,item按数字从低到高的顺序,通过 pipeline,通常将这些数字定义在0-1000范围内。

将 item 写入 MongoDB

  • from_crawler(cls, crawler):如果使用,这类方法被调用创建爬虫管道实例。必须返回管道的一个新实例。crawler提供存取所有Scrapy核心组件配置和信号管理器; 对于pipelines这是一种访问配置和信号管理器 的方式。参数: crawler (Crawler object) – crawler that uses this pipeline

例子中,我们将使用 pymongo 将 Item 写到 MongoDB。MongoDB 的地址和数据库名称在 Scrapy setttings.py 配置文件中;这个例子主要是说明如何使用 from_crawler() 方法

import pymongo

class MongoPipeline(object):

    collection_name = 'scrapy_items'

    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        self.mongo_db = mongo_db

    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI'),
            mongo_db=crawler.settings.get('MONGO_DATABASE', 'items')
        )

    def open_spider(self, spider):
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]

    def close_spider(self, spider):
        self.client.close()

    def process_item(self, item, spider):
        self.db[self.collection_name].insert(dict(item))
        return item

4、Spider、CrawlSpider

scrapy 为我们提供了5种 spider 用于构造请求,解析数据、返回 item。

常用的就是:scrapy.spider、scrapy.crawlspider两种。

class scrapy.spider.Spider

Spider类 定义了如何爬取某个(或某些)网站。包括了爬取的动作(例如:是否跟进链接) 以及如何从网页的内容中提取结构化数据(爬取item)。 换句话说,Spider 就是定义爬取的动作及分析某个网页(或者是有些网页)的地方。

Spider 是最简单的 spider。每个 spider 必须继承自该类。Spider 并没有提供什么特殊的功能。其仅仅请求给定的 start_urls / start_requests,并根据返回的结果调用 spider 的 parse 方法。

  • name:定义 spider 名字的字符串。例如,如果spider爬取 mywebsite.com ,该 spider 通常会被命名为 mywebsite
  • allowed_domains:可选。包含了spider允许爬取的域名(domain)列表(list)
  • start_urls:初始 URL 列表。当没有制定特定的 URL 时,spider 将从该列表中开始进行爬取。
  • start_requests():当 spider 启动爬取并且未指定 start_urls 时,该方法被调用。如果您想要修改最初爬取某个网站。
  • parse(self, response):当请求 url 返回网页没有指定回调函数时,默认下载回调方法。参数:response (Response) – 返回网页信息的 response
  • log(message[, level, component]):使用 scrapy.log.msg() 方法记录(log)message。 更多数据请参见 Logging

下面是 spider 常用到的 属性 和 方法。( 想要了解更多,可以查看源码 )

属性、方法 功能 简述
name 爬虫的名称 启动爬虫的时候会用到
start_urls 起始 url 是一个列表,默认被 start_requests 调用
allowd_doamins 对 url 进行的简单过滤

当请求 url 没有被 allowd_doamins 匹配到时,会报一个非常恶心的错,

start_requests() 第一次请求 自己的 spider 可以重写,突破一些简易的反爬机制
custom_settings 定制 settings 可以对每个爬虫定制 settings 配置
from_crawler 实例化入口 在 scrapy 的各个组件的源码中,首先执行的就是它

关于 spider 我们可以定制 start_requests、可以单独的设置 custom_settings、也可以设置请

例如,如果您需要在启动时以 POST 登录某个网站,你可以这么写:

class MySpider(scrapy.Spider):
    name = 'myspider'

    def start_requests(self):
        return [scrapy.FormRequest("http://www.example.com/login",
                                   formdata={'user': 'john', 'pass': 'secret'},
                                   callback=self.logged_in)]

    def logged_in(self, response):
        # here you would extract links to follow and return Requests for
        # each of them, with another callback
        pass

Spider 示例

让我们来看一个例子:

import scrapy

class MySpider(scrapy.Spider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = [
        'http://www.example.com/1.html',
        'http://www.example.com/2.html',
        'http://www.example.com/3.html',
    ]

    def parse(self, response):
        self.log('A response from %s just arrived!' % response.url)

另一个在单个回调函数中返回多个 Request 以及 Item 的例子:

import scrapy
from myproject.items import MyItem

class MySpider(scrapy.Spider):
    name = 'example.com'
    allowed_domains = ['example.com']
    start_urls = [
        'http://www.example.com/1.html',
        'http://www.example.com/2.html',
        'http://www.example.com/3.html',
    ]

    def parse(self, response):
        sel = scrapy.Selector(response)
        for h3 in response.xpath('//h3').extract():
            yield MyItem(title=h3)

        for url in response.xpath('//a/@href').extract():
            yield scrapy.Request(url, callback=self.parse)

案例:腾讯招聘网翻页功能

import scrapy
from tutorial.items import RecruitItem
import re
class RecruitSpider(scrapy.Spider):
    name = "tencent"
    allowed_domains = ["hr.tencent.com"]
    start_urls = [
        "http://hr.tencent.com/position.php?&start=0#a"
    ]

    def parse(self, response):
        for sel in response.xpath('//*[@class="even"]'):
            name = sel.xpath('./td[1]/a/text()').extract()[0]
            detailLink = sel.xpath('./td[1]/a/@href').extract()[0]
            catalog =None
            if sel.xpath('./td[2]/text()'):
                catalog = sel.xpath('./td[2]/text()').extract()[0]

            recruitNumber = sel.xpath('./td[3]/text()').extract()[0]
            workLocation = sel.xpath('./td[4]/text()').extract()[0]
            publishTime = sel.xpath('./td[5]/text()').extract()[0]
            #print name, detailLink, catalog,recruitNumber,workLocation,publishTime
            item = RecruitItem()
            item['name']=name.encode('utf-8')
            item['detailLink']=detailLink.encode('utf-8')
            if catalog:
                item['catalog']=catalog.encode('utf-8')
            item['recruitNumber']=recruitNumber.encode('utf-8')
            item['workLocation']=workLocation.encode('utf-8')
            item['publishTime']=publishTime.encode('utf-8')
            yield item

        nextFlag = response.xpath('//*[@id="next"]/@href')[0].extract()
        if 'start' in nextFlag:
            curpage = re.search('(\d+)',response.url).group(1)
            page =int(curpage)+10
            url = re.sub('\d+',str(page),response.url)
            print url
            yield scrapy.Request(url, callback=self.parse)

执行:scrapy crawl tencent -L INFO

scrapy.spiders.CrawlSpider

CrawlSpider 定义了一些规则(rule)来提供跟进 link 的方便的机制。除了从 Spider 继承过来的(您必须提供的)属性外(name、allow_domains),其提供了一个新的属性:

  • rules:包含一个(或多个) 规则对象的集合(list)。 每个Rule对爬取网站的动作定义了特定操作。 如果多个 rule 匹配了相同的链接,则根据规则在本集合中被定义的顺序,第一个会被使用。
  • parse_start_url(response):当 start_url 的请求返回时,该方法被调用

爬取规则(Crawling rules)

class scrapy.contrib.spiders.Rule(link_extractor, callback=None, cb_kwargs=None, follow=None, process_links=None, process_request=None)

  • link_extractor:其定义了如何从爬取到的页面中提取链接。
  • callback:指定 spider 中哪个函数将会被调用。 从 link_extractor 中每获取到链接时将会调用该函数。该回调函数接受一个response 作为其第一个参数。注意:当编写爬虫规则时,请避免使用 parse作为回调函数。由于 CrawlSpider 使用 parse 方法来实现其逻辑,如果您覆盖了 parse方法,crawl spider将会运行失败。
  • cb_kwargs:包含传递给回调函数的参数 (keyword argument) 的字典。
  • follow:是一个布尔(boolean)值,指定了根据该规则从response提取的链接是否需要跟进。 如果callback为None,follow默认设置为True ,否则默认为False。
  • process_links:指定该spider中哪个的函数将会被调用,从link_extractor中获取到链接列表时将会调用该函数。该方法常用于过滤参数
  • process_request:指定该spider中哪个的函数将会被调用,该规则提取到每个request时都会调用该函数 (用来过滤request)

CrawlSpider 案例

还是以腾讯招聘为例,给出配合 rule 使用 CrawlSpider 的例子:

首先运行

scrapy shell "http://hr.tencent.com/position.php?&start=0#a"

导入匹配规则:

from scrapy.linkextractors import LinkExtractor
page_lx = LinkExtractor(allow=('position.php?&start=\d+'))

查询匹配结果:

page_lx.extract_links(response)

没有查到:

page_lx = LinkExtractor(allow=(r'position\.php\?&start=\d+'))
page_lx.extract_links(response)

[Link(url='http://hr.tencent.com/position.php?start=10', text='2', fragment='', nofollow=False),
Link(url='http://hr.tencent.com/position.php?start=20', text='3', fragment='', nofollow=False),
Link(url='http://hr.tencent.com/position.php?start=30', text='4', fragment='', nofollow=False),
Link(url='http://hr.tencent.com/position.php?start=40', text='5', fragment='', nofollow=False),
Link(url='http://hr.tencent.com/position.php?start=50', text='6', fragment='', nofollow=False),
Link(url='http://hr.tencent.com/position.php?start=60', text='7', fragment='', nofollow=False),
Link(url='http://hr.tencent.com/position.php?start=70', text='...', fragment='', nofollow=False),
Link(url='http://hr.tencent.com/position.php?start=1300', text='131', fragment='', nofollow=False)]
len(page_lx.extract_links(response))

那么,scrapy shell 测试完成之后,修改以下代码

#提取匹配 'http://hr.tencent.com/position.php?&start=\d+'的链接
page_lx = LinkExtractor(allow=('start=\d+'))

rules = [
#提取匹配,并使用spider的parse方法进行分析;并跟进链接(没有callback意味着follow默认为True)
Rule(page_lx, callback='parse',follow=True)
]

这么写对吗? callback 千万不能写 parse,一定运行有错误!!

保存代码为 tencent_crawl.py

# -*- coding:utf-8 -*-
import scrapy
from tutorial.items import RecruitItem
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class RecruitSpider(CrawlSpider):
    name = "tencent_crawl"
    allowed_domains = ["hr.tencent.com"]
    start_urls = [
        "http://hr.tencent.com/position.php?&start=0#a"
    ]
    #提取匹配 'http://hr.tencent.com/position.php?&start=\d+'的链接
    page_lx = LinkExtractor(allow=('start=\d+'))

    rules = [
        #提取匹配,并使用spider的parse方法进行分析;并跟进链接(没有callback意味着follow默认为True)
        Rule(page_lx, callback='parseContent',follow=True)
    ]

    def parseContent(self, response):
        print response.url
        for sel in response.xpath('//*[@class="even"]'):
            name = sel.xpath('./td[1]/a/text()').extract()[0]
            detailLink = sel.xpath('./td[1]/a/@href').extract()[0]
            catalog =None
            if sel.xpath('./td[2]/text()'):
                catalog = sel.xpath('./td[2]/text()').extract()[0]

            recruitNumber = sel.xpath('./td[3]/text()').extract()[0]
            workLocation = sel.xpath('./td[4]/text()').extract()[0]
            publishTime = sel.xpath('./td[5]/text()').extract()[0]
            #print name, detailLink, catalog,recruitNumber,workLocation,publishTime
            item = RecruitItem()
            item['name']=name.encode('utf-8')
            item['detailLink']=detailLink.encode('utf-8')
            if catalog:
                item['catalog']=catalog.encode('utf-8')
            item['recruitNumber']=recruitNumber.encode('utf-8')
            item['workLocation']=workLocation.encode('utf-8')

            item['publishTime']=publishTime.encode('utf-8')
            yield item

可以修改配置文件settings.py,添加 LOG_LEVEL='INFO'

爬虫教程( 2 ) --- scrapy 教程、实战_第10张图片

运行: scrapy crawl tencent_crawl

process_links 参数:动态网页爬取,动态 url 的处理

在爬取 https://bitsharestalk.org 的时候,发现网站会为每一个 url 增加一个 sessionid 属性,可能是为了标记用户访问历史,而且这个 seesionid 随着每次访问都会动态变化,这就为爬虫的去重处理(即标记已经爬取过的网站)和提取规则增加了难度。

比如:General Discussion 会变成 https://bitsharestalk.org/index.phpPHPSESSID=9771d42640ab3c89eb77e8bd9e220b53&board=5.0

下面介绍几种处理方法

仅适用你的爬虫使用的是 scrapy.contrib.spiders.CrawlSpider,在这个内置爬虫中,你提取 url 要通过 Rule类来进行提取,其自带了对提取后的 url 进行加工的函数。

    rules = (
        Rule(
            LinkExtractor(
                allow=(
                    r"https://bitsharestalk\.org/index\.php\?PHPSESSID\S*board=\d+\.\d+$",
                    r"https://bitsharestalk\.org/index\.php\?board=\d+\.\d+$"
                )
            ),
            process_links='link_filtering'  # 默认函数process_links
        ),  

        Rule(
            LinkExtractor(
                allow=(
                    r" https://bitsharestalk\.org/index\.php\?PHPSESSID\S*topic=\d+\.\d+$",
                    r"https://bitsharestalk\.org/index\.php\?topic=\d+\.\d+$",
                ), 
            ),
            callback="extractPost",
            follow=True, process_links='link_filtering'
        ),

        Rule(
            LinkExtractor(
                allow=(
                    r"https://bitsharestalk\.org/index\.php\?PHPSESSID\S*action=profile;u=\d+$",
                    r"https://bitsharestalk\.org/index\.php\?action=profile;u=\d+$",
                ), 
            ),
            callback="extractUser", 
            process_links='link_filtering'
        )
    )

    def link_filtering(self, links):
        ret = []
        for link in links:
            url = link.url

        # print "This is the yuanlai ", link.url
        urlfirst, urllast = url.split(" ? ")

        if urllast:
            link.url = urlfirst + " ? " + urllast.split(" & ", 1)[1]

        # print link.url
        return links

link_filtering() 函数对 url 进行了处理,过滤掉了 sessid,关于 Rule类的 process_links 函数和 links 类,官方文档中并没有给出介绍,给出一个参考 https://groups.google.com/forum/#!topic/scrapy-users/RHGtm_2GO1M(也许需要梯子,你懂得)

如果你是自己实现的爬虫,那么 url 的处理更是可定制的,只需要自己处理一下就可以了。

process_request 参数:修改请求参数

import re
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor


class WeiboSpider(CrawlSpider):
    name = 'weibo'
    allowed_domains = ['weibo.com']

    # 不加www,则匹配不到 cookie, get_login_cookie()方法正则代完善
    start_urls = ['http://www.weibo.com/u/1876296184']
    rules = (
        Rule(
            # 微博个人页面的规则,或/u/或/n/后面跟一串数字
            LinkExtractor(allow=r'^http:\/\/(www\.)?weibo.com/[a-z]/.*'),
            process_request='process_request',
            callback='parse_item', follow=True
        ),
    )
    cookies = None

    def process_request(self, request):
        link = request.url
        page = re.search(r'page=\d*', link).group()
        tp = re.search(r'type=\d+', link).group()
        new_request = request.replace(
            cookies=self.cookies, 
            url='.../questionType?' + page + "&" + tp
        )
        return new_request

5、Logging

Scrapy 提供了 log 功能。您可以通过 logging 模块使用。

Log levels

Scrapy 提供5层 logging 级别:

  1. CRITICAL     ---  严重错误(critical)
  2. ERROR        ---  一般错误(regular errors)
  3. WARNING    ---  警告信息(warning messages)
  4. INFO             ---  一般信息(informational messages)
  5. DEBUG         ---  调试信息(debugging messages)

默认情况下 python 的 logging 模块将日志打印到了标准输出中,且只显示了大于等于 WARNING 级别的日志,这说明默认的日志级别设置为 WARNING(日志级别等级CRITICAL > ERROR > WARNING > INFO > DEBUG,默认的日志格式为DEBUG级别

如何设置 log 级别

您可以通过终端选项(command line option) --loglevel/-L 或 LOG_LEVEL 来设置log级别。

  • scrapy crawl tencent_crawl -L INFO

  • 可以修改配置文件 settings.py,添加  LOG_LEVEL='INFO'

scrapy crawl tencent_crawl -L INFO
也可以修改配置文件settings.py,添加 LOG_LEVEL='INFO'

爬虫教程( 2 ) --- scrapy 教程、实战_第11张图片

在 Spider 中添加 log

Scrapy 为每个 Spider 实例记录器提供了一个 logger,可以这样访问:

import scrapy

class MySpider(scrapy.Spider):

    name = 'myspider'
    start_urls = ['http://scrapinghub.com']

    def parse(self, response):
        self.logger.info('Parse function called on %s', response.url)

logger 是用 Spider 的名称创建的,但是你可以用你想要的任何自定义 logging。例如:

import logging
import scrapy

logger = logging.getLogger('zhangsan')

class MySpider(scrapy.Spider):

    name = 'myspider'
    start_urls = ['http://scrapinghub.com']

    def parse(self, response):
        logger.info('Parse function called on %s', response.url)

Logging 设置

以下设置可以被用来配置logging:
LOG_ENABLED        默认: True,启用logging
LOG_ENCODING       默认: 'utf-8',logging使用的编码
LOG_FILE           默认: None,logging输出的文件名
LOG_LEVEL          默认: 'DEBUG',log的最低级别
LOG_STDOUT         默认: False。如果为 True,进程所有的标准输出(及错误)将会被重定向到log中。
                   例如,执行 print 'hello' ,其将会在Scrapy log中显示。

案例 (一) ( self.logger )

tencent_crawl.py 添加日志信息如下:

    '''
    添加日志信息
    '''
    print 'print',response.url

    self.logger.info('info on %s', response.url)
    self.logger.warning('WARNING on %s', response.url)
    self.logger.debug('info on %s', response.url)
    self.logger.error('info on %s', response.url)

完整版如下:

# -*- coding:utf-8 -*-
import scrapy
from tutorial.items import RecruitItem
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class RecruitSpider(CrawlSpider):
    name = "tencent_crawl"
    allowed_domains = ["hr.tencent.com"]
    start_urls = [
        "http://hr.tencent.com/position.php?&start=0#a"
    ]


    #提取匹配 'http://hr.tencent.com/position.php?&start=\d+'的链接
    page_lx = LinkExtractor(allow=('start=\d+'))

    rules = [
        #提取匹配,并使用spider的parse方法进行分析;并跟进链接(没有callback意味着follow默认为True)
        Rule(page_lx, callback='parseContent',follow=True)
    ]

    def parseContent(self, response):

        #print("print settings: %s" % self.settings['LOG_FILE'])
        '''
        添加日志信息
        '''
        print 'print',response.url

        self.logger.info('info on %s', response.url)
        self.logger.warning('WARNING on %s', response.url)
        self.logger.debug('info on %s', response.url)
        self.logger.error('info on %s', response.url)


        for sel in response.xpath('//*[@class="even"]'):
            name = sel.xpath('./td[1]/a/text()').extract()[0]
            detailLink = sel.xpath('./td[1]/a/@href').extract()[0]
            catalog =None
            if sel.xpath('./td[2]/text()'):
                catalog = sel.xpath('./td[2]/text()').extract()[0]

            recruitNumber = sel.xpath('./td[3]/text()').extract()[0]
            workLocation = sel.xpath('./td[4]/text()').extract()[0]
            publishTime = sel.xpath('./td[5]/text()').extract()[0]
            #print name, detailLink, catalog,recruitNumber,workLocation,publishTime
            item = RecruitItem()
            item['name']=name
            item['detailLink']=detailLink
            if catalog:
                item['catalog']=catalog
            item['recruitNumber']=recruitNumber
            item['workLocation']=workLocation

            item['publishTime']=publishTime
            yield item

在 settings 文件中,修改添加信息

LOG_FILE='ten.log'
LOG_LEVEL='INFO'

接下来执行:scrapy crawl tencent_crawl。或者 command line 命令行执行:

scrapy crawl tencent_crawl --logfile 'ten.log' -L INFO

输出如下

print http://hr.tencent.com/position.php?start=10
print http://hr.tencent.com/position.php?start=1340
print http://hr.tencent.com/position.php?start=0
print http://hr.tencent.com/position.php?start=1320
print http://hr.tencent.com/position.php?start=1310
print http://hr.tencent.com/position.php?start=1300
print http://hr.tencent.com/position.php?start=1290
print http://hr.tencent.com/position.php?start=1260

ten.log 文件中记录,可以看到级别大于 INFO 日志输出

2016-08-15 23:10:57 [tencent_crawl] INFO: info on http://hr.tencent.com/position.php?start=70
2016-08-15 23:10:57 [tencent_crawl] WARNING: WARNING on http://hr.tencent.com/position.php?start=70
2016-08-15 23:10:57 [tencent_crawl] ERROR: info on http://hr.tencent.com/position.php?start=70
2016-08-15 23:10:57 [tencent_crawl] INFO: info on http://hr.tencent.com/position.php?start=1320
2016-08-15 23:10:57 [tencent_crawl] WARNING: WARNING on http://hr.tencent.com/position.php?start=1320
2016-08-15 23:10:57 [tencent_crawl] ERROR: info on http://hr.tencent.com/position.php?start=1320

案例(二)( logging.getLogger )

tencent_spider.py 添加日志信息如下:logger = logging.getLogger('zhangsan')

    '''
    添加日志信息
    '''
    print 'print',response.url

    self.logger.info('info on %s', response.url)
    self.logger.warning('WARNING on %s', response.url)
    self.logger.debug('info on %s', response.url)
    self.logger.error('info on %s', response.url)

完整版如下:

import scrapy
from tutorial.items import RecruitItem
import re
import logging

logger = logging.getLogger('zhangsan')

class RecruitSpider(scrapy.spiders.Spider):
    name = "tencent"
    allowed_domains = ["hr.tencent.com"]
    start_urls = [
        "http://hr.tencent.com/position.php?&start=0#a"
    ]

    def parse(self, response):
        #logger.info('spider tencent Parse function called on %s', response.url)
        '''
        添加日志信息
        '''
        print 'print',response.url

        logger.info('info on %s', response.url)
        logger.warning('WARNING on %s', response.url)
        logger.debug('info on %s', response.url)
        logger.error('info on %s', response.url)

        for sel in response.xpath('//*[@class="even"]'):
            name = sel.xpath('./td[1]/a/text()').extract()[0]
            detailLink = sel.xpath('./td[1]/a/@href').extract()[0]
            catalog =None
            if sel.xpath('./td[2]/text()'):
                catalog = sel.xpath('./td[2]/text()').extract()[0]

            recruitNumber = sel.xpath('./td[3]/text()').extract()[0]
            workLocation = sel.xpath('./td[4]/text()').extract()[0]
            publishTime = sel.xpath('./td[5]/text()').extract()[0]
            #print name, detailLink, catalog,recruitNumber,workLocation,publishTime
            item = RecruitItem()
            item['name']=name
            item['detailLink']=detailLink
            if catalog:
                item['catalog']=catalog
            item['recruitNumber']=recruitNumber
            item['workLocation']=workLocation
            item['publishTime']=publishTime
            yield item

        nextFlag = response.xpath('//*[@id="next"]/@href')[0].extract()
        if 'start' in nextFlag:
            curpage = re.search('(\d+)',response.url).group(1)
            page =int(curpage)+10
            url = re.sub('\d+',str(page),response.url)
            print url
            yield scrapy.Request(url, callback=self.parse)

在 settings 文件中,修改添加信息

LOG_FILE='tencent.log'
LOG_LEVEL='WARNING'

接下来执行:scrapy crawl tencent 。或者command line命令行执行:

scrapy crawl tencent --logfile 'tencent.log' -L WARNING

输出信息

print http://hr.tencent.com/position.php?&start=0
http://hr.tencent.com/position.php?&start=10
print http://hr.tencent.com/position.php?&start=10
http://hr.tencent.com/position.php?&start=20
print http://hr.tencent.com/position.php?&start=20
http://hr.tencent.com/position.php?&start=30

tencent.log 文件中记录,可以看到级别大于 INFO 日志输出

2016-08-15 23:22:59 [zhangsan] WARNING: WARNING on http://hr.tencent.com/position.php?&start=0
2016-08-15 23:22:59 [zhangsan] ERROR: info on http://hr.tencent.com/position.php?&start=0
2016-08-15 23:22:59 [zhangsan] WARNING: WARNING on http://hr.tencent.com/position.php?&start=10
2016-08-15 23:22:59 [zhangsan] ERROR: info on http://hr.tencent.com/position.php?&start=10

小试 LOG_STDOUT

settings.py

LOG_FILE='tencent.log'
LOG_STDOUT=True
LOG_LEVEL='INFO'

执行:scrapy crawl tencent

输出:空

tencent.log 文件中记录,可以看到级别大于 INFO 日志输出

2016-08-15 23:28:32 [stdout] INFO: http://hr.tencent.com/position.php?&start=110
2016-08-15 23:28:32 [stdout] INFO: print
2016-08-15 23:28:32 [stdout] INFO: http://hr.tencent.com/position.php?&start=110
2016-08-15 23:28:32 [zhangsan] INFO: info on http://hr.tencent.com/position.php?&start=110
2016-08-15 23:28:32 [zhangsan] WARNING: WARNING on http://hr.tencent.com/position.php?&start=110
2016-08-15 23:28:32 [zhangsan] ERROR: info on http://hr.tencent.com/position.php?&start=110
2016-08-15 23:28:32 [stdout] INFO: http://hr.tencent.com/position.php?&start=120
2016-08-15 23:28:33 [stdout] INFO: print
2016-08-15 23:28:33 [stdout] INFO: http://hr.tencent.com/position.php?&start=120
2016-08-15 23:28:33 [zhangsan] INFO: info on http://hr.tencent.com/position.php?&start=120
2016-08-15 23:28:33 [zhangsan] WARNING: WARNING on http://hr.tencent.com/position.php?&start=120
2016-08-15 23:28:33 [zhangsan] ERROR: info on http://hr.tencent.com/position.php?&start=120

scrapy 之 Logging 使用

#coding:utf-8
######################
##Logging的使用
######################
import logging
'''
1. logging.CRITICAL - for critical errors (highest severity) 致命错误
2. logging.ERROR - for regular errors 一般错误
3. logging.WARNING - for warning messages 警告+错误
4. logging.INFO - for informational messages 消息+警告+错误
5. logging.DEBUG - for debugging messages (lowest severity) 低级别
'''
logging.warning("This is a warning")

logging.log(logging.WARNING,"This is a warning")

#获取实例对象
logger=logging.getLogger()
logger.warning("这是警告消息")
#指定消息发出者
logger = logging.getLogger('SimilarFace')
logger.warning("This is a warning")

#在爬虫中使用log
import scrapy
class MySpider(scrapy.Spider):
    name = 'myspider'
    start_urls = ['http://scrapinghub.com']
    def parse(self, response):
        #方法1 自带的logger
        self.logger.info('Parse function called on %s', response.url)
        #方法2 自己定义个logger
        logger.info('Parse function called on %s', response.url)

'''
Logging 设置
• LOG_FILE
• LOG_ENABLED
• LOG_ENCODING
• LOG_LEVEL
• LOG_FORMAT
• LOG_DATEFORMAT • LOG_STDOUT

命令行中使用
--logfile FILE
Overrides LOG_FILE

--loglevel/-L LEVEL
Overrides LOG_LEVEL

--nolog
Sets LOG_ENABLED to False
'''

import logging
from scrapy.utils.log import configure_logging

configure_logging(install_root_handler=False)
#定义了logging的些属性
logging.basicConfig(
    filename='log.txt',
    format='%(levelname)s: %(levelname)s: %(message)s',
    level=logging.INFO
)
#运行时追加模式
logging.info('进入Log文件')
logger = logging.getLogger('SimilarFace')
logger.warning("也要进入Log文件")

6、Settings

Scrapy 设置(settings)提供了定制 Scrapy 组件的方法。可以控制包括核心(core),插件(extension),pipeline 及 spider 组件。比如 设置 Json Pipeliine、LOG_LEVEL

内置设置列表请参考内置设置参考手册

获取设置值 (Populating the settings)

设置可以通过多种方式设置,每个方式具有不同的优先级。

下面以 优先级降序 的方式给出方式列表:

  • 命令行选项(Command line Options)(最高优先级) 。命令行传入的参数具有最高的优先级。 使用选项 -s (或 --set) 来覆盖一个 (或更多) 选项。比如:scrapy crawl myspider -s LOG_FILE=scrapy.log
  • 每个 spider 的设置 ( scrapy.spiders.Spider.custom_settings )。
    class MySpider(scrapy.Spider):
      name = 'myspider'
    
      custom_settings = {
          'SOME_SETTING': 'some value',
      }
  • 项目设置模块 (Project settings module)。项目设置模块是 Scrapy 项目的标准配置文件。即 setting.py
    myproject.settings

如何访问配置 (settings)

In a spider, the settings are available through self.settings:

class MySpider(scrapy.Spider):
    name = 'myspider'
    start_urls = ['http://example.com']

    def parse(self, response):
        print("Existing settings: %s" % self.settings.attributes.keys())

Settings can be accessed through the scrapy.crawler.Crawler.settings attribute of the Crawler that is passed to from_crawler method in extensions, middlewares and item pipelines:

class MyExtension(object):
    def __init__(self, log_is_enabled=False):
        if log_is_enabled:
            print("log is enabled!")

    @classmethod
    def from_crawler(cls, crawler):
        settings = crawler.settings
        return cls(settings.getbool('LOG_ENABLED'))

案例 ( self.settings 使用 )

添加一行代码 print("Existing settings: %s" % self.settings['LOG_FILE'])

# -*- coding:utf-8 -*-
import scrapy
from tutorial.items import RecruitItem
from scrapy.contrib.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
import logging


class RecruitSpider(CrawlSpider):
    name = "tencent_crawl"
    allowed_domains = ["hr.tencent.com"]
    start_urls = [
        "http://hr.tencent.com/position.php?&start=0#a"
    ]
    #提取匹配 'http://hr.tencent.com/position.php?&start=\d+'的链接
    page_lx = LinkExtractor(allow=('start=\d+'))

    rules = [
        #提取匹配,并使用spider的parse方法进行分析;并跟进链接(没有callback意味着follow默认为True)
        Rule(page_lx, callback='parseContent',follow=True)
    ]

    def parseContent(self, response):
        print response.url
        print("Existing settings: %s" % self.settings['LOG_FILE'])


        self.logger.info('Parse function called on %s', response.url)
        for sel in response.xpath('//*[@class="even"]'):
            name = sel.xpath('./td[1]/a/text()').extract()[0]
            detailLink = sel.xpath('./td[1]/a/@href').extract()[0]
            catalog =None
            if sel.xpath('./td[2]/text()'):
                catalog = sel.xpath('./td[2]/text()').extract()[0]

            recruitNumber = sel.xpath('./td[3]/text()').extract()[0]
            workLocation = sel.xpath('./td[4]/text()').extract()[0]
            publishTime = sel.xpath('./td[5]/text()').extract()[0]
            #print name, detailLink, catalog,recruitNumber,workLocation,publishTime
            item = RecruitItem()
            item['name']=name.encode('utf-8')
            item['detailLink']=detailLink.encode('utf-8')
            if catalog:
                item['catalog']=catalog.encode('utf-8')
            item['recruitNumber']=recruitNumber.encode('utf-8')
            item['workLocation']=workLocation.encode('utf-8')

            item['publishTime']=publishTime.encode('utf-8')
            yield item

内置设置参考手册

BOT_NAME:默认: 'scrapybot'。当您使用 startproject 命令创建项目时其也被自动赋值。
CONCURRENT_ITEMS:默认: 100。Item Processor(即 Item Pipeline) 同时处理(每个response的)item的最大值。
CONCURRENT_REQUESTS:默认: 16。Scrapy downloader 并发请求(concurrent requests)的最大值。
DEFAULT_REQUEST_HEADERS 默认:
{
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'en',
}
Scrapy HTTP Request使用的默认header。
DEPTH_LIMIT:默认: 0。爬取网站最大允许的深度(depth)值。如果为0,则没有限制。
DOWNLOAD_DELAY:默认: 0。下载器在下载同一个网站下一个页面前需要等待的时间。该选项可以用来限制爬取速度, 减轻服务器压力。同时也支持小数:
DOWNLOAD_DELAY = 0.25 # 250 ms of delay:
该设置影响(默认启用的) RANDOMIZE_DOWNLOAD_DELAY 设置。 默认情况下,Scrapy在两个请求间不等待一个固定的值, 而是使用0.5到1.5之间的一个随机值 * DOWNLOAD_DELAY 的结果作为等待间隔。
DOWNLOAD_TIMEOUT:默认: 180。下载器超时时间(单位: 秒)。
ITEM_PIPELINES:默认: {}。保存项目中启用的pipeline及其顺序的字典。该字典默认为空,值(value)任意。 不过值(value)习惯设置在0-1000范围内。
样例:
ITEM_PIPELINES = {
'mybot.pipelines.validate.ValidateMyItem': 300,
'mybot.pipelines.validate.StoreMyItem': 800,
}
LOG_ENABLED:默认: True。是否启用logging。
LOG_ENCODING:默认: 'utf-8'。logging使用的编码。
LOG_LEVEL:默认: 'DEBUG'。log的最低级别。可选的级别有: CRITICAL、 ERROR、WARNING、INFO、DEBUG 。
USER_AGENT:默认: "Scrapy/VERSION (+http://scrapy.org)"。爬取的默认User-Agent,除非被覆盖。

阳光热线问政平台( 东莞 )

目标网址:http://wz.sun0769.com/political/index/politicsNewest?id=1&type=4

items.py:添加以下代码

from scrapy.item import Item, Field

class SunItem(Item):
    number = Field()
    url = Field()
    title = Field()
    content = Field()

在 spiders 目录下新建一个自定义 SunSpider.py

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
# from tutorial.items import SunItem
import scrapy
import urllib
import time
import re


class SunSpider(CrawlSpider):
    name = 'sun0769'
    num = 0
    allow_domain = ['http://wz.sun0769.com/']
    start_urls = [
        'http://wz.sun0769.com/political/index/politicsNewest?id=1&type=4'
    ]

    rules = {
        Rule(LinkExtractor(allow='page'), process_links='process_request', follow=True),
        Rule(LinkExtractor(allow=r'/html/question/\d+/\d+\.shtml$'), callback='parse_content')
    }

    def process_request(self, links):
        ret = []

        for link in links:
            try:
                page = re.search(r'page=\d*', link.url).group()
                tp = re.search(r'type=\d+', link.url).group()
                link.url = 'http://wz.sun0769.com/index.php/question/questionType?' + page + "&" + tp
            except BaseException as e:
                print(e)
            ret.append(link)
        return ret

    def parse_content(self, response):
        item = SunItem()
        url = response.url
        title = response.xpath('//*[@class="greyframe"]/div/div/strong/text()')[0].extract().strip()
        number = response.xpath(
            '//*[@class="greyframe"]/div/div/strong/text()'
        )[0].extract().strip().split(':')[-1]
        content = response.xpath('//div[@class="c1 text14_2"]/text()').extract()[0].strip()

        item['url'] = url
        item['title'] = title
        item['number'] = number
        item['content'] = content
        print(dict(item))
        # yield item


if __name__ == '__main__':
    from scrapy import cmdline
    cmdline.execute('scrapy crawl sun0769'.split())
    pass

在 pipelines.py:添加如下代码

import json
import codecs

class JsonWriterPipeline(object):

    def __init__(self):
        self.file = codecs.open('items.json', 'w', encoding='utf-8')

    def process_item(self, item, spider):
        line = json.dumps(dict(item), ensure_ascii=False) + "\n"
        self.file.write(line)
        return item

    def spider_closed(self, spider):
        self.file.close()

settings.py 添加如下代码(启用组件)

ITEM_PIPELINES = {
    'tutorial.pipelines.JsonWriterPipeline': 300,
}

window 下调试

在项目根目录下新建 main.py 文件,用于调试

from scrapy import cmdline
cmdline.execute('scrapy crawl sun0769'.split())

7、部署、可视化 管理 爬虫

爬虫 可视化 管理平台:Gerapy、crawllab、crawlab-lite

爬虫可视化管理平台、爬虫可视化调度工具,分布式爬虫管理框架、scrapy 可视化调度工具

框架 技术 优点 缺点
Crawlab

Golang

Vue

不限于Scrapy,适用于所有编程语言和框架。漂亮的UI界面。自然地支持分布式蜘蛛。支持蜘蛛管理,任务管理,cron作业,结果导出,分析,通知,可配置蜘蛛,在线代码编辑器等。 暂不支持版本控制
ScrapydWeb

Python Flask

Vue

漂亮的UI界面,内置Scrapy日志解析器,任务执行的统计数据和图表,支持节点管理,cron作业,邮件通知,移动。全功能蜘蛛管理平台。 不支持除 Scrapy 以外的其他蜘蛛。由于后端Python Flask性能有限。
Gerapy

Python Django

Vue

Gerapy是由崔庆才创建的。安装部署简单。漂亮的UI界面。支持节点管理、代码编辑、可配置抓取规则等。 同样不支持除Scrapy以外的其他蜘蛛。根据用户反馈,1.0版有很多bug。期待v2.0中的改进
SpiderKeeper

Python Flask

开源Scrapyhub。简洁明了的UI界面。支持cron作业。 可能太简单了,不支持分页,不支持节点管理,不支持Scrapy以外的蜘蛛。

Gerapy、crawllab、crawlab-lite

  • SpiderKeeper 可能是最早的爬虫管理平台,但功能相对来说比较局限;
  • Gerapy 虽然功能齐全,界面精美,但有不少 bug 需要处理。
  • Scrapydweb 是一个比较完善的爬虫管理平台,不过和前两者一样,都是基于 scrapyd 的,因此只能运行 scrapy 爬虫;
  • Crawlab是一个非常灵活的爬虫管理平台,可以运行 Python、Nodejs、Java、PHP、Go 写的爬虫,而且功能比较齐全,只是部署起来相对于前三者来说要麻烦一些,不过对于 Docker 使用者来说可以做到一键部署。

大多数现有的平台都依赖于 Scrapyd,这将选择限制在 python 和 scrapy 之间。当然 scrapy 是一个很棒的网络抓取框架,但是它不能做所有的事情。

对于重度 scrapy 爬虫依赖的、又不想折腾的开发者,可以考虑 Scrapydweb;而对于有各种类型的、复杂技术结构的爬虫开发者来说,可以考虑更灵活的 Crawlab。当然,不是说 Crawlab 对 scrapy 支持不友好,Crawlab 同样可以很好的集成 scrapy,也很容易使用,足够通用,可以适应任何语言和框架中的蜘蛛。它还有一个漂亮的前端界面,用户可以更容易地管理蜘蛛。

你可能感兴趣的:(Python,爬虫,爬虫,scrapy,python)