python爬虫的最佳实践(七)--pyspider框架的使用

Ps:惯例ps环节,经过前面几章的学习,写一些简单的爬虫已经是手到擒来了吧。这章我们看看如何使用pyspider框架来写爬虫~

环境配置

在这之前简单介绍一下为什么选择这个框架:

  1. 这个框架是共和国人民写的(最强大的理由,没有之一)。
  2. 其次,有webUi的界面,如果你有服务器,那么随时随地都可以编写爬虫,在线调试的机制也做得很好。
  3. 多任务并行,健全的任务管理机制。
  4. 支持各种主流数据库。

建议大家去看看文档,因为我这里只能介绍少部分功能,后续得还需要大家自己在使用的时候自行挖掘~pyspider文档

  • mac环境
    mac环境是很好配置的,只需要打开你的pycharm,然后直接输入pyspider安装即可~
    或者在终端pip install pyspider
1.png
  • windows环境
    同mac环境,最好直接在pycharm中安装

  • linux环境
    因为这个框架一般都是在linux服务器上运行的,本机对框架的需求不大。
    1.ubuntu系统:
    apt-get install python-dev python-distribute libcurl4-openssl-dev libxml2-dev libxslt1-dev python-lxml
    安装上述的依赖环境,等待安装完成后执行pip install pyspider
    这样就安装完成了
    2.centos系统
    centos的话建议使用7系列的版本,这样py直接是2.7,否则还得自己安装py2.7以上的版本。
    安装依赖yum install -y gcc ruby-devel libxml2 libxml2-devel libxslt libxslt-devel
    然后同样的pip install pyspider
    即可完成安装

在终端输入pyspider启动框架,进入http://localhost:5000/,如图所示:

2.png

这样就可以开始使用了~

代码预览

import re
from pyspider.libs.base_handler import *

class Handler(BaseHandler):

    @every(minutes=24 * 60)
    def on_start(self):
        self.crawl('http://www.imdb.com/search/title?count=100&title_type=feature,tv_series,tv_movie&ref_=nv_ch_mm_1', callback=self.index_page)

    @config(age=24 * 60 * 60)
    def index_page(self, response):
        for each in response.doc('a[href^="http"]').items():
            if re.match("http://www.imdb.com/title/tt\\d+/$", each.attr.href):
                self.crawl(each.attr.href, callback=self.detail_page)
        self.crawl([x.attr.href for x in response.doc('#right a').items()], callback=self.index_page)

    @config(priority=2)
    def detail_page(self, response):
        return {
            "url": response.url,
            "title": response.doc('.header > [itemprop="name"]').text(),
            "rating": response.doc('.star-box-giga-star').text(),
            "director": [x.text() for x in response.doc('[itemprop="director"] span').items()],
        }

代码剖析

原谅博主偷了个懒,这段代码不是博主自己敲的,主要是下班回家写博客确实很累,而且这段代码在pyspider的官方demo,运行情况如下,用来做入门很不错~


3.png

好了,不说废话了。这次我们的目标是IMDb,非常有名的电影资料网站。首先,我们看到这段代码是否觉得和我们上次看到的unittest结构很类似?没错,首先,我们创建的class Handler继承pyspider的BaseHandler。程序的入口是on_start()函数,这段代码没有写crawl_config,一般如果 网站反爬虫机制很强的话,会带上crawl_config,结构如下:

crawl_config = {
        "headers" : headers,
        "timeout" : 1000,
        "cookies" : Cookie
        "proxy" : 192.168.1.1:8888
    }

这个我们先简单提一下,在随后的课程再详细介绍。

我们注意到在on_start()函数上方有这样一段代码@every(minutes=24 * 60),这是告诉scheduler(调度器)每天调度一次on_start()函数。

self.crawl('http://www.imdb.com/search/title?count=100&title_type=feature,tv_series,tv_movie&ref_=nv_ch_mm_1',callback=self.index_page)

crawl()函数的两个参数,前一个指的是url,后一个指的是回调函数,也就是执行完请求之后调用的函数。

接下来回调指向了index_page()
@config(age=24 * 60 * 60)
这个设置代表了在一天之内忽略已经请求过的网站,一般都会设置的比every的时间长一点,保证少做重复工作,但是这样写也没有问题,因为我们扒的网站信息是在不断更新的,并不是一成不变的。crawl会返回一个response对象,所有的返回信息都在这个对象里面。

for each in response.doc('a[href^="http"]').items():
        if re.match("http://www.imdb.com/title/tt\\d+/$", each.attr.href):
              self.crawl(each.attr.href, callback=self.detail_page)
        self.crawl([x.attr.href for x in response.doc('#right a').items()], callback=self.index_page)

response.doc是一个PyQuery对象,支持Css Selector。
上面第一行代码找出返回网页源代码里面所有的网页链接,然后通过re(python自带的正则模块),匹配出所有格式为http://www.imdb.com/title/tt开头后面跟着若干数字的网页。如http://www.imdb.com/title/tt3040964/
然后挨个请求self.crawl(each.attr.href, callback=self.detail_page),回调为detail_page

最后一行代码是一个递归的调用,就是如果这一页有翻译按钮,那么久请求下一页并递归调用index_page()函数,这样就可以把网页中所有的电影全部抓取下来。

return {
            "url": response.url,
            "title": response.doc('.header > [itemprop="name"]').text(),
            "rating": response.doc('.star-box-giga-star').text(),
            "director": [x.text() for x in response.doc('[itemprop="director"] span').items()],
        }

detail_page函数是用来吧结果储存在数据库中的,如果我们需要对数据进行过滤,还有on_result(self, result)函数,它会传入result作为参数。重写on_result的函数之后我们就可以对数据进行过滤。比如过滤掉一些无用的数据。上面的四行数据处理的代码如果看不懂可以参看前面讲过的知识。

至此,今天的代码剖析完毕。今天只是讲了一下pyspider的简单应用,pyspider还有很多很实用的方法可以去发觉,比如和phantomJS结合,抓取ajax的动态数据,只需要你的服务器有phantomJs环境,然后再crawl函数中加入self.crawl('http://www.twitch.tv/directory/game/Dota%202', fetch_type='js', callback=self.index_page)fetch_type即可。

最后我讲一下pyspider的管理台界面都有什么作用,参见上图的2.png,第一个group我也不是太清楚作用,只知道把group设置为delete然后status设置为stop,24小时之后任务会自动被删除。我们要开始一个任务,需要吧status设置为RUNNING然后在点击后面的run按钮。rate指的是每秒抓取多少次网页,一般情况下建议1以下,不容易被封。brust指的是并发度~后面的active Tasks可以看到现在正在执行的task的状态,最后一个就是抓取的result。

写在最后

今天我们讲解了pyspider的简单使用,下一章我们讲一下scrapy的简单使用,让大家能上手这个爬虫界的利器。

最后附上我前两天讲解抓瓜子网信息的代码。


#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Created on 2016-05-01 11:31:38
# Project: guazi

from pyspider.libs.base_handler import *
import pymongo

class Handler(BaseHandler):
    client = pymongo.MongoClient('localhost', 27017)
    guazi2 = client['guazi2']
    car = guazi2['car']
    
    crawl_config = {
    }

    @every(minutes=24 * 60)
    def on_start(self):
        self.crawl('http://www.guazi.com/bj/buy', callback=self.index_page)

    @config(age=10 * 24 * 60 * 60)
    def index_page(self, response):
        for each in response.doc('body > div.header > div.hd-top.clearfix > div.c2city > div > div > dl > dd > a').items():
            self.crawl(each.attr.href, callback=self.second_page)

    @config(age=10 * 24 * 60 * 60)
    def second_page(self, response):
        num = int(response.doc('div.seqBox.clearfix > p > b').text())
        urls = [response.url+'o{}/'.format(str(i)) for i in range(1,num/40+2,1)]
        for each in urls:
            self.crawl(each, callback=self.third_page)
            
    @config(age=10 * 24 * 60 * 60)
    def third_page(self, response):
        for each in response.doc('div.list > ul > li > div > a').items():
            self.crawl(each.attr.href, callback=self.detail_page)
            
    @config(priority=2)
    def detail_page(self, response):
        return {
            "url": response.url,
            "title": response.doc('body > div.w > div > div.laybox.clearfix > div.det-sumright.appoint > div.dt-titbox > h1').text(),
            "address": response.doc('body > div.w > div > div.laybox.clearfix > div.det-sumright.appoint > ul > li:nth-child(5) > b').text(),
            "cartype": response.doc('body > div.w > div > div.laybox.clearfix > div.det-sumright.appoint > ul > li:nth-child(3) > b').text(),
            "price": response.doc('body > div.w > div > div.laybox.clearfix > div.det-sumright.appoint > div.basic-box > div.pricebox > span.fc-org.pricestype > b').text().replace(u'¥',''),
            "region":response.doc('#base > ul > li.owner').text()
        }
    
    def on_result(self, result):
        self.car.insert_one(result)

大家做个参考写的不好,轻喷

有兴趣的同学可以加群498945822一起交流学习哦~~
发现问题的同学欢迎指正,直接说就行,不用留面子,博主脸皮厚!

推荐阅读更多精彩内容