crwy说明文档

介绍

Crwy是一个轻量级的爬虫抓取框架,参考Scrapy框架结构开发而来,并为scrapy使用者提供通用轮子^.^。该框架提供了实用的爬虫模板,旨在帮助大家快速实现爬虫任务,高效开发。新增了gevent,使爬虫异步执行,速度更快。

版本

v1.5.0

安装

运行环境

  • Python2 & Python3
  • Works on Linux, Mac OSX

快速安装

1
pip install crwy

源码包安装

  1. 从这里下载:
    https://codeload.github.com/wuyue92tree/crwy/zip/master

  2. 下载完成后,解压缩并进入crwy包目录,执行如下命令

1
python setup.py install

若出现依赖包安装失败的情况,可先执行

1
pip install -r requirements.txt

安装成功便可开始你的crwy之旅了。

命令行工具介绍

开始

在终端中键入: crwy, 将在屏幕上看到如下显示

1
2
3
4
5
6
7
8
9
10
11
12
13
Crwy - no active project found!!!

Usage:
crwy <commands> [option] [args]

Avaliable Commands:
list list all spider in your project
runspider run a spider
startproject create a new project
createspider create a new spider
version show version

Use "crwy <command> -h" to see more info about a command

可以看到crwy支持list, runspider, startproject, createspider等命令,想知道它们都是怎么用的么?继续往下看吧。

startproject

该命令用以新建爬虫项目

1
crwy startproject spidertest

执行成功会得到如下返回:

Project start……enjoy^.^

那么该命令到底干了些什么呢?

1
2
3
4
5
6
7
8
spidertest
├── crwy.cfg 确认项目名称及settings所在目录
├── data 爬取结果存储目录(sqlite存储的默认路径)
│   └── __init__.py
├── spidertest
│   └── settings.py 项目配置文件
└── src 爬虫所在目录
└── __init__.py

它建立了一个名为: spidertest的工程, 里面包含了爬虫将用到的配置(将在后面详细解释)。

createspider

该命令用以新建爬虫
添加”-h”参数,看该命令如何使用:

1
crwy createspider -h

执行成功会得到如下返回:

1
2
3
4
5
6
7
8
9
10
Usage:  crwy createspider [option] [args]

Options:
-h, --help show this help message and exit
-l, --list list available spider template name
-p PREVIEW, --preview=PREVIEW
preview spider template
-t TEMPLATE, --tmpl=TEMPLATE
spider template
-n NAME, --name=NAME new spider name
  • -l: 用以列举可用的爬虫模板
  • -p: 用以查看模板代码
  • -t: 用以指定继承的模板名称
  • -n: 用以指定将要创建的爬虫的名称

例子:

1
crwy createspider -t basic -n basictest

便可在src目录中找到生成的相对应的爬虫程序
注意: 创建爬虫时需在项目根目录下(即:crwy.cfg文件所在目录),否则项目将创建失败。

list

该命令用以显示爬虫列表

1
crwy list

runspider

该命令用以执行爬虫

添加”-h”参数,看该命令如何使用:

1
crwy runspider -h

1
2
3
4
5
6
7
8
9
Usage:  crwy runspider [option] [args]

Options:
-h, --help show this help message and exit
-n NAME, --name=NAME spider name
-c COROUTINE, --coroutine=COROUTINE
crawler by multi coroutine
-t THREAD, --thread=THREAD
crawler by multi thread
  • -n: 用以指定将要执行的爬虫的名称
  • -c: 用以控制程序采用多协程运行(-c参数后接协程数)
  • -t: 用以控制程序采用多线程运行(-t参数后接线程数)

version

该命令用以查看crwy版本号

1
crwy version

爬虫模板介绍

basic模板

basic模板包含最基本的抓取逻辑

  • 下载: html_downloader
  • 解析: html_parser

模板内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function

from crwy.spider import Spider


class ${class_name}Spider(Spider):
def __init__(self):
Spider.__init__(self)
self.spider_name = '${spider_name}'

def crawler_${spider_name}(self):
try:
url = 'http://example.com'
response = self.html_downloader.download(url)
soups = self.html_parser.parser(response.content)
print(url)
print(soups)
self.logger.info('%s --> crawler success !!!' % self.spider_name)

except Exception as e:
self.logger.exception('%s --> %s' % (
self.spider_name, e))

def run(self):
self.crawler_${spider_name}()

可以看到,继承了一个名为Spider的类,该类中封装了html_downloader下载器和html_parser解析器,详情请阅读Utils详解中的Html章节。

sqlite模板

sqlite模板将爬取数据存储到sqlite数据库中

  • 下载: html_downloader
  • 解析: html_parser
  • 存储: sqlite

模板内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function

from sqlalchemy import Integer, Column, String
from crwy.spider import Spider
from crwy.utils.sql.db import Database, Base


class Test(Base):
__tablename__ = "test"
id = Column(Integer, primary_key=True, unique=True)
title = Column(String(20))
url = Column(String(20))


class ${class_name}Spider(Spider):
def __init__(self):
Spider.__init__(self)
self.spider_name = '${spider_name}'
self.sql = Database('sqlite:///./data/test.db')
self.sql.init_table()

def crawler_${spider_name}(self):
try:
url = 'http://example.com'
response = self.html_downloader.download(url)
soups = self.html_parser.parser(response.content)
title = soups.find('title').text
item = Test(title=title.decode('utf-8'), url=url.decode('utf-8'))
self.sql.session.merge(item)
self.sql.session.commit()
print(url)
print(soups)
self.logger.info('%s --> crawler success !!!' %
self.spider_name)

except Exception as e:
self.logger.exception('%s --> %s' % (
self.spider_name, e))

def run(self):
self.crawler_${spider_name}()

存储逻辑:

  1. 通过创建class继承Base类(该类继承自sqlalchemy的declarative_base)生成table
  2. 通过Database类连接sqlite数据库,执行init_table()创建数据表, Sqlite类是什么 [Click_] (04_utils.html#sql)。
  3. 调用session.merge()存入相关数据,调用session.commit()使更改生效

queue模板

queue模块将待爬取页面加载到队列中,实时把控队列进度

  • 寻找待爬取页面规则,将页面URL压入队列
  • 从队列中取出一个URL
  • 下载: html_downloader
  • 解析: html_parser

模板内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function

import sys
import Queue
from crwy.spider import Spider

queue = Queue.Queue()


class ${class_name}Spider(Spider):
def __init__(self):
Spider.__init__(self)
self.spider_name = '${spider_name}'

def crawler_${spider_name}(self):
while True:
try:
if not queue.empty():
url = 'http://example.com/%d' % queue.get()
response = self.html_downloader.download(url)
soups = self.html_parser.parser(response.content)
print(url)
print(soups)
print('Length of queue : %d' % queue.qsize())
else:
self.logger.info('%s --> crawler success !!!' %
self.spider_name)
sys.exit()

except Exception as e:
self.logger.exception('%s --> %s' % (
self.spider_name, e))
continue

def run(self):
for i in range(1, 10):
queue.put(i)

self.crawler_${spider_name}()

队列为多线程提供好的入口。

redis_queue模板

redis_queue模板将队列持久化到redis服务器中,以解决服务器宕机导致任务丢失的问题

  • 连接redis服务器: RedisQueue, 新建队列
  • 寻找待爬取页面规则,将页面URL压入队列
  • 从队列中取出一个URL
  • 下载: html_downloader
  • 解析: html_parser

模板内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function

import sys
from crwy.spider import Spider
from crwy.utils.queue.RedisQueue import RedisQueue
from crwy.utils.filter.RedisSet import RedisSet


queue = RedisQueue('foo')
s_filter = RedisSet('foo')


class ${class_name}Spider(Spider):
def __init__(self):
Spider.__init__(self)
self.spider_name = '${spider_name}'

def crawler_${spider_name}(self):
while True:
try:
if not queue.empty():
url = 'http://example.com/%s' % queue.get()
if s_filter.sadd(url) is False:
print('You got a crawled url. %s' % url)
continue
response = self.html_downloader.download(url)
soups = self.html_parser.parser(response.content)
print(url)
print(soups)
print('Length of queue : %s' % queue.qsize())
else:
self.logger.info('%s --> crawler success !!!' %
self.spider_name)
sys.exit()

except Exception as e:
self.logger.exception('%s --> %s' % (
self.spider_name, e))
continue

def add_queue(self):
for i in range(100):
queue.put(i)
print(queue.qsize())

def run(self):
try:
worker = sys.argv[4]
except :
print('No worker found!!!\n')
sys.exit()

if worker == 'crawler':
self.crawler_${spider_name}()
elif worker == 'add_queue':
self.add_queue()
elif worker == 'clean':
queue.clean()
s_filter.clean()
else:
print('Invalid worker <%s>!!!\n' % worker)

添加add_queue()方法,可实现在程序不中断的情况下,继续添加新的抓取目标。

Utils详解

html

html_downloader

采用requests做为下载器引擎

本框架采用版本 2.12.0

  • download(url, method=’GET’, timeout=60)

    | url: 目标网站URL
    | method: 规定请求方式,默认为GET
    | timeout: 规定超时时间(默认为60)
    | **kwargs: 与requests保持一致

  • downloadFile(url, save_path=’./data/‘)

    | url: 目标文件URL
    | save_path: 文件保存路径

requests传送门: http://www.python-requests.org/en/master/

html_parser

采用BeautifulSoup4做为解析器引擎

  • parser(response)

    | 解析UTF-8编码网页

  • gbk_parser(response)

    | 解析GBK编码网页

  • jsonp_parser(response)

    | 解析不规则json网页(key不带双引号),返回dict

beautifulsoup4传送门: https://www.crummy.com/software/BeautifulSoup/

sql

sqlalchemy_m (原db)

采用sqlalchemy操作数据库
具体支持数据库,参考:http://docs.sqlalchemy.org/en/latest/core/engines.html

  • init(db_url, **kwargs)

    | db_url为数据库地址

  • init_table()

    | 初始化数据库

  • drop_table()

    | 清空数据库

sqlalchemy传送门: http://www.sqlalchemy.org/

mysql

mysql api

pg

postgrel api

no_sql

redis_m

redis api

queue

RedisQueue

如何优雅的将redis当成消息队列

  • init(name, namespace=’queue’, **redis_kwargs)

    | name: 队列名称
    | namespace: 命名空间(默认为queue)
    | **redis_kwargs: redis模块初始化参数

  • qsize()
    返回队列长度

  • empty()
    队列为空时返回True

  • put()
    向队列中压入一条数据

  • get()
    从队列中取出一条数据

  • get_nowait()
    从队列中取出一条数据,不阻塞

  • clean()
    清空队列

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author: wuyue92tree@163.com

import redis


class RedisQueue(object):
"""Simple Queue with Redis Backend"""

def __init__(self, name, namespace='queue', **redis_kwargs):
"""The default connection parameters are: host='localhost', port=6379, db=0"""
self.__db = redis.Redis(**redis_kwargs)
self.key = '%s:%s' % (namespace, name)

def qsize(self):
"""Return the approximate size of the queue."""
return self.__db.llen(self.key)

def empty(self):
"""Return True if the queue is empty, False otherwise."""
return self.qsize() == 0

def put(self, item):
"""Put item into the queue."""
self.__db.rpush(self.key, item)

def get(self, block=True, timeout=None):
"""Remove and return an item from the queue.

If optional args block is true and timeout is None (the default), block
if necessary until an item is available."""
if block:
item = self.__db.blpop(self.key, timeout=timeout)
else:
item = self.__db.lpop(self.key)

if item:
item = item[1]
return item

def get_nowait(self):
"""Equivalent to get(False)."""
return self.get(False)

def clean(self):
"""Empty key"""
return self.__db.delete(self.key)

SsdbQueue

如何优雅的将SSDB当成消息队列

  • init(name, **ssdb_kwargs)

    | name: 队列名称
    | **ssdb_kwargs: ssdb模块初始化参数

  • qsize()
    返回队列长度

  • empty()
    队列为空时返回True

  • put()
    向队列中压入一条数据

  • get()
    从队列中取出一条数据

  • clean()
    清空队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# author: wuyue92tree@163.com

import pyssdb


class SsdbQueue(object):
"""Simple Queue with SSDB Backend"""

def __init__(self, name, **ssdb_kwargs):
"""The default connection parameters are: host='localhost', port=8888"""
self.__db = pyssdb.Client(**ssdb_kwargs)
self.key = name

def qsize(self):
"""Return the approximate size of the queue."""
return self.__db.qsize(self.key)

def empty(self):
"""Return True if the queue is empty, False otherwise."""
return self.qsize() == 0

def put(self, item):
"""Put item into the queue."""
self.__db.qpush(self.key, item)

def get(self):
"""Remove and return an item from the queue.

If optional args block is true and timeout is None (the default), block
if necessary until an item is available."""

item = self.__db.qpop(self.key)

return item

def clean(self):
"""Empty key"""
return self.__db.qclear(self.key)

data

RedisHash

对redis hash类型数据操作的封装,多用于数据存储, like: cookie

filter

RedisSet

对redis set类型数据操作的封装,多用于数据去重

RedisSortSet

对redis zset类型数据操作的封装,多用于代标签数据的去重(按时间段去重)

scrapy_plugs

通用的scrapy中间件、过滤器

extend

damatu

验证码打码平台(打码兔),已下线

chaojiying

验证码打码平台(超级鹰),正常使用

yima

手机验证码接码平台(易码),正常使用

xuma

手机验证码接码平台(讯码),正常使用

tianma168

手机验证码接码平台(天马168),已停用

dingding_robot

钉钉消息推送api

selenium_api

selenium封装,测试版本 v3.6.0

日志系统

日志采用配置文件的形式工作

eg: logger.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#logger.conf
###############################################
[loggers]
keys=root,fileLogger,rtLogger,timedRtLogger

[logger_root]
level=INFO
handlers=consoleHandler

[logger_fileLogger]
handlers=consoleHandler,fileHandler
qualname=fileLogger
propagate=0

[logger_rtLogger]
handlers=consoleHandler,rtHandler
qualname=rtLogger
propagate=0

[logger_timedRtLogger]
handlers=consoleHandler,timedRtHandler
qualname=timedRtLogger
propagate=0

###############################################
[handlers]
keys=consoleHandler,fileHandler,rtHandler,timedRtHandler

[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=simpleFmt
args=(sys.stderr,)

[handler_fileHandler]
class=FileHandler
level=DEBUG
formatter=defaultFmt
args=('./log/default.log', 'a')

[handler_rtHandler]
class=handlers.RotatingFileHandler
level=DEBUG
formatter=defaultFmt
args=('./log/default.log', 'a', 100*1024*1024, 10)

[handler_timedRtHandler]
class=handlers.TimedRotatingFileHandler
level=DEBUG
formatter=defaultFmt
args=('./log/default.log', 'M', 1, 10)


###############################################

[formatters]
keys=defaultFmt,simpleFmt

[formatter_defaultFmt]
format=%(asctime)s %(filename)s %(funcName)s %(threadName)s [line:%(lineno)d] %(levelname)s %(message)s
datefmt=%Y-%m-%d %H:%M:%S

[formatter_simpleFmt]
format=%(asctime)s %(threadName)s %(levelname)s %(message)s
datefmt=%Y-%m-%d %H:%M:%S

FAQ手册

整装待发……

更新日志

2019-01-11 v1.5.0

2019-10-09 v1.4.0

2018-12-24 v1.3.2

  • fix pg connect issue.

2018-12-18 v1.3.1

  • fix firefox webdriver proxy issue;
  • add jiyan slide tools.

2018-12-07 v1.3.0

2018-11-08 v1.2.0

2018-10-18 v1.1.3

  • 添加selenium_api封装;
  • xunma,yima api兼容py3;
  • scrapy修复cookie issue。

2018-09-20 v1.1.2

  • 调整scrapy-plugs的redis中间件,connect复用scrapy-redis;
  • redis相关组件支持外部传入connect对象;
  • 添加讯码手机打码api。

2018-08-27 v1.1.1

  • 更新cookiemiddlewares;
  • 添加SqlalchemySavePipeline。

2018-08-24 v1.1.0

  • 修复python3中bs4告警问题;
  • 移除打码兔及天马168,添加易码平台;
  • 添加字体解析工具类;
  • 添加cookiemiddleware。

2018-07-01 v1.0.9

  • set添加srem方法,修改部分说明信息;
  • 添加依赖retrying,imapclient;
  • 添加超级鹰api;
  • last_insert_id返回调整;
  • 为redis添加单例模式,保证线程内连接池共享;
  • 为sql模块对象添加单例模式支持,实现连接池共享及更名db模块为sqlalchemy;
  • 新增scrapy_plugs自定义常用中间件;
  • RedisRFPDupeFilter添加SPIDER_NAME检查;
  • 添加有序集合,用于采集时,进行按时间点去重;
  • 添加scrapy_plugs;
  • 添加从consul加载配置文件逻辑。;
  • 添加cls_singleton关键词用于控制单例模式是否生效;
  • 添加zscore;
  • 添加DUPEFILTER_DELAY_DAY选项,若该值大于0,则采用有序集合进行去重,time.time()时间戳作为score;
  • dict2obj 添加change_dict选项,用于控制是否转换target内部dict为obj;
  • 修复通过zscore判断失效时间时,时间计算错误问题;
  • 添加过滤日志;
  • 添加aby代理下载中间件;
  • 在meta选项中添加对duperliter_delay_day监听,添加DUPEFILTER_DO_HASH选项;
  • 修改代理中间件;
  • 移动redis_m模块到no_sql。

2018-05-11 v1.0.8

  • 下载器添加非session支持
  • download_file添加参数描述,添加file_name用于指定文件保存名称
  • 更改日志文件默认参数
  • RedisHash添加hlen返回hash长度
  • 更改PyV8模块引入
  • 更新logger默认采用timed_rt_logger, 基本方法添加get_cookie, 添加login_kwargs用于存放登录所需参数
  • 去除IMAPClient依赖
  • 新增装饰器模块,当前包含cls_catch_exception和cls_refresh_cookie
  • 新增exceptions模块
  • utils中添加common模块 用以包含常用工具方法
  • 暴露self.__db以获取更多原始包功能
  • utils中common模块 添加datetime2str, str2datetime, dict2obj, obj2dict, conf… …
  • 添加mysql_handle
  • 去除部分依赖
  • 添加kafka,mns,import Exception
  • 调整mysql, pg连接池
  • 更改SpiderBase类为BaseSpider,将logger作为可变参数在Spider类初始化时传入
  • 新增CrwyExtendException并应用到dingding_robot/tianma168模块
  • 更新MailReceiver接口
  • utils模块中, __init__.py添加逻辑导入
  • 添加 get_redis_client()
  • 通过拦截重写handler的方式传入log_path,实现日志位置修改
  • Redis改用连接池
  • 修复pg库import问题
  • 添加打马兔api,调整mysql_handle,增加last_insert_id回写
  • 拆分search和fetch的封装,解决邮件过大导致下载失败问题
  • mysql模块切换为pymysql
  • 修改ImportError提示信息
  • 添加字典kv调换方法
  • 迁移说明文档

2017-11-14 v1.0.7

  • utils工具包中添加extend模块,用于添加第三方调用api;
  • 升级mail包,改用imapclient接收解析邮件。

2017-09-21 v1.0.6

  • 日志新增timedRtLogger模板及自定义Logger调用接口
  • 爬虫执行脚本新增thread支持
  • 修改项目创建脚本,配置文件固定在conf目录

2017-06-13 v1.0.5

  • 解决pypi版本问题。

2017-06-12 v1.0.4

  • 修改默认日志conf模板,RedisSet模块添加返回Set所有内容。

2017-06-01 v1.0.4

  • 日志模块/邮件模块关联剥离。

2017-05-19 v1.0.4

  • 下载器更换为requests, 并新增打文件下载方式;
  • 新增RedisSet模块充当网页去重过滤器;
  • 新增RedisHash模块,用于存储cookies等需持久化参数;
  • 新增Logger模块,将默认日志集成到spider中,简化templates;
  • 将内置的多进程启动更换为多协程,多进程直接由外部方式实现,框架不再支持;
  • 优化templates。

2017-04-17 v1.0.3

  • 修改下载器,支持自定义headers传入。

2017-04-04 v1.0.3

  • 加入gevent,实现pycurl与gevent异步调用;
  • 新增async异步模板;
  • 修改HtmlDownloader返回值,返回Response对象。

2017-03-22 v1.0.2

  • docs更新多进程,redis/ssdb队列文档。

2017-02-14 v1.0.2

  • runspider模块新增多进程支持。

2017-02-07 v1.0.2

  • 更改RedisQueue模块路径,新增SsdbQueue模块。

2017-01-09 v1.0.2

  • 修复模板中的BUG;
  • 去除mysqldb依赖,用户根据自行需求进行安装;
  • 讲utils中的sqlite包名称更改为db,且功能上更新为通用数据链接。