Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/gxtrobot/aspider

a spider using asyncio
https://github.com/gxtrobot/aspider

Last synced: about 2 months ago
JSON representation

a spider using asyncio

Awesome Lists containing this project

README

        

## aspider
一个简单好用的Python 异步爬虫, 你只要需要做两件事就能编写一个爬虫
- 发现新链接(提供一个匹配新链接正则表达式)
- 提供一个页面内容处理函数

## 新手快速上手教程
- [文档](./docs/aspider_lesson.md) [pdf]((./docs/aspider_lesson.pdf))
- [视频](https://www.bilibili.com/video/av74316859/)

## 简介

aspider 是一个基于Python asyncio 开发的爬虫工具, 可以通过命令行使用, 不用写任何代码, 也可以调用api, 自定义具体爬取的规则.
aspider的设计原则是专注于爬取网页, 本身不包含如解析文本, 数据存储等内容, 这些都可以利用很多已有的工具框架自行加上. aspider 参考了
类似flask的路由装饰器的方法, 只需要定义一些方法, 每个方法用于爬取一个相应的正则路径.

author: gxtrobot

## 系统设计原理
- 系统使用常用的生产者, 消费者模式, 生产者产生需要爬取的链接并加入队列, 消费者从队列获取链接进行爬取, 并解析其内容和链接, 调用相关的方法, 和用户自定义的方法, 符合要求的链接会加到队列, 这样只通过一个根路径就可以扩展到全站爬取
- 系统才用asyncio的并发协程, 每个并发协程同时担任消费者和生产者的指责, 通过一个queue(队列)进行交互
- 系统定义了一个路由(Router), 作为用户唯一需要使用的对象, 路由不需要新建, 系统只有一个Router对象, 直接获取就可以使用
- 用户所有自定义方法, 都通过路由提供的装饰器来实现, 例如route, 无需创建class或其他复杂的逻辑

## 安装

To install use pip:

$ pip install aspider

Or clone the repo:

$ git clone https://github.com/gxtrobot/aspider.git
$ python setup.py install

## 开始使用

### 命令行方式
aspider 安装完成后可以直接通过命令 ```aspider``` 使用, 或 ```python -m aspider```
```
aspider --help
usage: aspider [-h] [--max_redirect N] [--max_tries N] [--max_tasks N]
[-p PROXY] [-i INCLUDE_REGEX] [-e EXCLUDE_REGEX] [-o OUTPUT]
[-c COUNT] [--nostrict] [--no_parse_links] [-v] [-q]
[roots [roots ...]]

An Asyncio Web crawler, version: 0.1.0

positional arguments:
roots 要爬取的网页路径(一般为跟路径), 可以有多个

optional arguments:
-h, --help 显示帮助信息
--max_tasks N 并发任务数, 默认为5个, 如需要可以提高
-p PROXY, --proxy PROXY
代理使用, 例如 http://localhost:1080
-i INCLUDE_REGEX, --include INCLUDE_REGEX
需要匹配爬取的网页路径正则, 匹配的网页链接将被加入爬取队列
-o OUTPUT, --output OUTPUT
输出的方法, 目前支持这几种[txt, json, stream], stream为命令行输出
-c COUNT, --count COUNT
限制爬取的数量, 如指定, 到限额后爬虫自动退出, 队列未完成的会取消
--no_parse_links 禁止解析链接, 指定后, 只爬取命令行给定的roots链接, 不会跟踪解
页面包含的链接
-v, --verbose debug 消息数据, 可以有多个v (-v , -vv)
-q, --quiet 静默输出, 只打印错误消息
```
### 命令行使用例子
- 豆瓣250电影抓取

运行以下命令, 将会爬取top250的10页html, 并存到json文件中

```
aspider https://movie.douban.com/top250 --include /top250.* --out json
```
不过这通常并没太大用处, 因为这样只是抓取完整的html, 只适合有时候需要抓取整个网页, 然后用其他工具处理, 或者不懂python 开发, 可以先用这个抓取网页, 然后用其他工具处理

保存的json文件是json line格式, 就是说每行是一个json对象, 读取时候要相应处理, 每读取一行, 将该行内容转为json对象, python 代码如下
```
def read_json_lines():
file = 'top250.json'
with open(file) as f:
for line in f:
d = json.loads(line)
print(d.keys()
```

## API 介绍

### routing 模块 - Router 对象

- route(rule, verify_func=None, no_parse_links=False)
```
装饰器, 在用户自定义解析方法使用

Args:
rule: 处理path的正则表达式, 可以包含参数, 使用'<>'包住参数名就可以, 例如 '/page/', 这样用户自定义方法可以接收no参数

verify_func: 可选, 用户路径验证方法, 如果提供可以自定义是否处理该路径, 方法签名为 verify_url(path), return True or False

no_parse_links: 可选, 是否处理该页面上的其他链接, 如果需要关闭解析该页面链接, 可以设置为True

用户被装饰方法定义如下:

@router.route('/page/', verify_page_path)
def process_page(text, path, no):

Args:
text: 当前要解析页面的html代码, 可以使用其他框架如lxml, request_html, BeautifulSoup
path: 可选, 如果有该参数, 系统会自动传递该path参数供使用
其他参数, 装饰器的自定义参数

注意: 前两个参数为系统自动提供, 顺序不能错, 用户自定义参数在前两个之后
```

### aspider 模块
该模块提供download方法启动爬虫

- download(loop=None, extra_args=None):
```
Args:
loop - 用户提供asyncio loop对象, 一般不需要, 除非是在编写定时任务, 多线程使用, 这时候用户生成一个loop对象并传入
extra_args - 其他参数, aspider 命令行使用的参数可以用该dict对象传入
其中, 必须的是roots key, 对应为抓取跟路径, 是一个list

Returns:
stats - 抓取过程统计对象, 包含抓取的网页数量, 完成时间等数据

example:

options = {
'roots': ['https://movie.douban.com/top250']
}
stats = aspider.download(extra_args=options)

```

### reporting 模块

- Stats 对象
该对象记录抓取过程的统计数据, 如抓取网页数量, 完成时间, 速度等

- report()
该方法打印这次抓取的记录报告

### API 调用爬虫例子

#### douban top250 电影抓取(代码在example 下))
1. 先取得router对象
2. 定义解析方法, 使用router.route装饰器, 提供一个参数为要解析的path正则表达式
3. 定义一个main方法启动爬虫就可以

```
'''
例子: douban top 250 电影名单爬取
'''
from collections import namedtuple
from aspider.routeing import get_router
from aspider import aspider
from requests_html import HTML

Movie = namedtuple('Movie', ['rank', 'score', 'title'])

router = get_router()

root_url = 'https://movie.douban.com/top250'

movies_250 = []

@router.route('/top250\?start.+')
def process_page(text):
html = HTML(html=text)
item_css = '#content ol.grid_view > li'
items = html.find(item_css)
rank_css = 'em'
title_css = '.info span.title'
score_css = '.info .rating_num'
for item in items:
rank = int(item.find(rank_css, first=True).text)
title = item.find(title_css, first=True).text
score = float(item.find(score_css, first=True).text)
movies_250.append(Movie(rank, score, title))

def main():
options = {
'roots': [root_url]
}
stats = aspider.download(extra_args=options)
stats.report()
fname = 'top250.txt'
sorted_movies_250 = sorted(movies_250, key=lambda m: m.rank)
with open(fname, 'w') as f:
for movie in sorted_movies_250:
print(f'#{movie.rank:<10} {movie.score:<10.2f} - {movie.title}')
print(f'#{movie.rank:<10} {movie.score:<10.2f} - {movie.title}', file=f)

if __name__ == "__main__":
main()

```