Tutorials

目标:通过对Hacker News的爬取来展示如何使用ruia,下图红框中的数据就是我们需要爬取的:

../../_images/tutorials_01.pngtutorials_01

假设我们将此项目命名为hacker_news_spider,项目结构如下:

hacker_news_spider
├── db.py
├── hacker_news.py
├── items.py
└── middlewares.py

Item

Item的目的是定义目标网站中你需要爬取的数据,此时,爬虫的目标数据就是页面中的TitleUrl,怎么提取数据,ruia提供了CSS SelectorXPath两种方式提取目标数据

Notice: 后续爬虫例子都默认使用CSS Selector的规则来提取目标数据

这里我们使用CSS Selector来提取目标数据,用浏览器打开Hacker News,右键审查元素:

../../_images/tutorials_02.pngtutorials_02

显而易见,每页包含30条资讯,那么目标数据的规则可以总结为:

| Param | Rule | Description | | :———- | —————– | ———————— | | target_item | tr.athing | 表示每条资讯 | | title | a.storylink | 表示每条资讯里的标题 | | url | a.storylink->href | 表示每条资讯里标题的链接 |

规则明确之后,就可以用Item来实现一个针对于目标数据的ORM,创建文件items.py,复制下面代码:

from ruia import AttrField, TextField, Item


class HackerNewsItem(Item):
    target_item = TextField(css_select='tr.athing')
    title = TextField(css_select='a.storylink')
    url = AttrField(css_select='a.storylink', attr='href')

只需要继承Item类,将目标参数定义为一个属性即可,如果目标数据是可以循环提取的,比如此时每一页里面有30条数据,那么就需要定义target_item来循环提取每一条数据里面的TitleUrl

Middleware

Middleware的目的是对每次请求前后进行一番处理,分下面两种情况:

  • 在每次请求之前做一些事
  • 在每次请求后做一些事

比如此时爬取Hacker News,你希望在每次请求时候自动添加HeadersUser-Agent,可以将下面代码复制到你建立的middlewares.py文件中:

from ruia import Middleware

middleware = Middleware()


@middleware.request
async def print_on_request(request):
    ua = 'ruia user-agent'
    request.headers.update({'User-Agent': ua})

这样,程序会在爬虫请求网页资源之前自动加上User-Agent

Database

对于数据持久化,你可以按照自己喜欢的方式去做,接下来我们将以MongoDB为例对爬取的数据进行存储,创建db.py文件:

import asyncio

from motor.motor_asyncio import AsyncIOMotorClient


class MotorBase:
    """
    About motor's doc: https://github.com/mongodb/motor
    """
    _db = {}
    _collection = {}

    def __init__(self, loop=None):
        self.motor_uri = ''
        self.loop = loop or asyncio.get_event_loop()

    def client(self, db):
        # motor
        self.motor_uri = f"mongodb://localhost:27017/{db}"
        return AsyncIOMotorClient(self.motor_uri, io_loop=self.loop)

    def get_db(self, db='test'):
        """
        Get a db instance
        :param db: database name
        :return: the motor db instance
        """
        if db not in self._db:
            self._db[db] = self.client(db)[db]

        return self._db[db]

Spider

Spider可以说是爬虫程序的入口,它将ItemMiddlewareRequest、等模块组合在一起,从而为你构造一个稳健的爬虫程序

这次的目的仅仅是为了演示如何使用ruia编写爬虫,所以这个例子仅仅爬取Hacker News的前两页数据,创建hacker_news.py文件:

from ruia import Request, Spider

from items import HackerNewsItem
from middlewares import middleware
from db import MotorBase


class HackerNewsSpider(Spider):
    start_urls = ['https://news.ycombinator.com']
    concurrency = 3

    async def parse(self, res):
        self.mongo_db = MotorBase().get_db('ruia_test')
        urls = ['https://news.ycombinator.com/news?p=1', 'https://news.ycombinator.com/news?p=2']
        for index, url in enumerate(urls):
            yield Request(
                url,
                callback=self.parse_item,
                metadata={'index': index}
            )

    async def parse_item(self, res):
        items = await HackerNewsItem.get_items(html=res.html)

        for item in items:
            try:
                await self.mongo_db.news.update_one({
                    'url': item.url},
                    {'$set': {'url': item.url, 'title': item.title}},
                    upsert=True)
            except Exception as e:
                self.logger.exception(e)


if __name__ == '__main__':
    HackerNewsSpider.start(middleware=middleware)

HackerNewsSpider继承于Spider类,其中子类必须实现parse()方法,运行python hacker_news.py

[2018-09-24 17:59:19,865]-ruia-INFO  spider : Spider started!
[2018-09-24 17:59:19,866]-Request-INFO  request: <GET: https://news.ycombinator.com>
[2018-09-24 17:59:23,259]-Request-INFO  request: <GET: https://news.ycombinator.com/news?p=1>
[2018-09-24 17:59:23,260]-Request-INFO  request: <GET: https://news.ycombinator.com/news?p=2>
[2018-09-24 18:03:05,562]-ruia-INFO  spider : Stopping spider: ruia
[2018-09-24 18:03:05,562]-ruia-INFO  spider : Total requests: 3
[2018-09-24 18:03:05,562]-ruia-INFO  spider : Time usage: 0:00:02.802862
[2018-09-24 18:03:05,562]-ruia-INFO  spider : Spider finished!

数据库中可以看到:

../../_images/tutorials_03.jpgtutorials_03

通过这个例子,你已经基本掌握了ruiaItemMiddlewareRequest等模块的用法,结合自身需求,你可以编写任何爬虫,例子代码见hacker_news_spider

接下来,我们将结合实例,编写一个ruia的第三方扩展,详见:Plugins