pyspider使用

一个国人编写的强大的网络爬虫系统并带有强大的WebUI。采用Python语言编写,分布式架构,支持多种数据库后端,强大的WebUI支持脚本编辑器,任务监视器,项目管理器以及结果查看器。

pyspider是作者之前做的一个爬虫架构的开源化实现。主要的功能需求是:

  • 抓取、更新调度多站点的特定的页面
  • 需要对页面进行结构化信息提取
  • 灵活可扩展,稳定可监控

这也是绝大多数python爬虫的需求:定向抓取,结构化化解析。但是面对结构迥异的各种网站,单一的抓取模式并不一定能满足,灵活的抓取控制是必须的。为了达到这个目的,单纯的配置文件往往不够灵活,于是,通过脚本去控制抓取是最后的选择。而去重调度,队列,抓取,异常处理,监控等功能作为框架,提供给抓取脚本,并保证灵活性。最后加上web的编辑调试环境,以及web任务监控,即成为了这套框架。

pyspider的设计基础是:以python脚本驱动的抓取环模型爬虫

  • 通过python脚本进行结构化信息的提取,follow链接调度抓取控制,实现最大的灵活性
  • 通过web化的脚本编写、调试环境。web展现调度状态
  • 抓取环模型成熟稳定,模块间相互独立,通过消息队列连接,从单进程到多机分布式灵活拓展

官方文档 / 最新版本

安装&&运行

pip install pyspider

安装成功后运行

pyspider

打开http://localhost:5000/ 访问控制台

创建新爬虫

在web控制台点create按钮新建任务,自定义项目名和爬取的起始网址。点击确定后打开代码编辑器(代码编辑器默认有简单的实例代码)。

右侧为代码编辑器,可直接添加和修改代码。代码简单分析:

  • on_start(self) 方法是入口代码。当在web控制台点击run按钮时会执行此方法。

    self.crawl(url, callback=self.index_page)这个方法是调用API生成一个新的爬取任务,这个任务被添加到待抓取队列。

  • index_page(self, response) 方法获取一个Response对象。 response.doc是pyquery对象的一个扩展方法。pyquery是一个类似于jQuery的对象选择器。

  • detail_ page(self, response)返回一个结果集对象。这个结果默认会被添加到resultdb数据库(如果启动时没有指定数据库默认调用sqlite数据库)。你也可以重写on_result(self,result)方法来指定保存位置。

  • @every(minutes=24*60, seconds=0) 这个设置是告诉scheduler(调度器)on_start方法每天执行一次。

  • @config(age=10 * 24 * 60 * 60) 这个设置告诉scheduler(调度器)这个request(请求)过期时间是10天,10天内再遇到这个请求直接忽略。这个参数也可以在self.crawl(url, age=102460*60) 和 crawl_config中设置。

  • @config(priority=2) 这个是优先级设置。数字越大越先执行。

完成脚本编写并调试无误后,先保存脚本,然后返回控制台首页。点击项目状态status,把状态由TODO改成debug或running,最后点击项目最右边RUN按钮启动项目。当progress那栏有数据显示说明启动成功,运行一段时间就可以在最右侧的results查看结果了。

数据存入Mysql

编写数据库脚本mysqldb.py(放入./site-packages/pyspider/database/mysql/下):

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from six import itervalues
import MySQLdb

class SQL():
    #数据库初始化
    def __init__(self):
        #数据库连接相关信息
        hosts    = '数据库地址'  
        username = '数据库用户名'
        password = '数据库密码'
        database = '数据库名'
        charsets = 'utf8'

        self.connection = False
        try:
            self.conn = MySQLdb.connect(host = hosts,user = username,passwd = password,db = database,charset = charsets)
            self.cursor = self.conn.cursor()
            self.cursor.execute("set names "+charsets)
            self.connection = True
        except Exception,e:
            print "Cannot Connect To Mysql!/n",e

    def escape(self,string):
        return '%s' % string
    #插入数据到数据库   
    def insert(self,tablename=None,**values):

        if self.connection: 
            tablename = self.escape(tablename)  
            if values:
                _keys = ",".join(self.escape(k) for k in values)
                _values = ",".join(['%s',]*len(values))
                sql_query = "insert into %s (%s) values (%s)" % (tablename,_keys,_values)
            else:
                sql_query = "replace into %s default values" % tablename
            try:
                if values:
                    self.cursor.execute(sql_query,list(itervalues(values)))
                else:       
                    self.cursor.execute(sql_query)
                self.conn.commit()
                return True
            except Exception,e:
                print "An Error Occured: ",e
                return False

注:此处使用的是MySQLdb驱动,所以需要确保已安装MySQLdb

在Pyspider的脚本开头引入:

from pyspider.database.mysql.mysqldb import SQL

重写on_result方法:

def on_result(self,result):
        if not result or not result['original_id']:
            return
        sql = SQL()
        sql.insert('t_dream_xm_project',**result)  

数据库新建数据库以及对应的表,启动服务器进行测试了。

完整脚本如下:

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Created on 2017-07-14 10:36:36
# Project: xiaomi

from pyspider.libs.base_handler import *
from pyspider.database.mysql.mysqldb import SQL
import urllib
import time
import json

class Handler(BaseHandler):
    #配置通用的请求属性
    crawl_config = {
        'headers' : {'Connection':'keep-alive','Accept-Encoding':'gzip, deflate, br','Accept-Language':'zh-CN,zh;q=0.8','content-type':'application/x-www-form-urlencoded','Referer':'//home.mi.com/crowdfundinglist?id=78&title=%E4%BC%97%E7%AD%B9','User-Agent':'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36'}
    }

    @every(minutes=24 * 60)
    def on_start(self):
        #获取所有的产品详细产品地址
        param = 'data=%7B%22HomeList%22%3A%7B%22model%22%3A%22Homepage%22%2C%22action%22%3A%22BuildHome%22%2C%22parameters%22%3A%7B%22id%22%3A12%7D%7D%7D'
        self.crawl('https://home.mi.com/app/shopv3/pipe',method="GET",params=param,callback=self.index_page)


    @config(age=60 * 60)  
    def index_page(self, response):
        #获取单个产品的详细信息
        for each in response.json['result']['HomeList']['data']:
            gid = each['gid']
            detailparm = "{\"detail\":{\"model\":\"Shopv2\",\"action\":\"getDetail\",\"parameters\":{\"gid\":\"%s\"}},\"comment\":{\"model\":\"Comment\",\"action\":\"getList\",\"parameters\":{\"goods_id\":\"%s\",\"orderby\":\"1\",\"pageindex\":\"0\",\"pagesize\":3}},\"activity\":{\"model\":\"Activity\",\"action\":\"getAct\",\"parameters\":{\"gid\":\"%s\"}}}" % (gid,gid,gid)
            detailreq = urllib.quote(detailparm)
            detailreq = "data=" + detailreq
            detailurl = "https://home.mi.com/app/shop/pipe?gid=%s" % gid 
            #print detailurl
            self.crawl(detailurl,method='POST',data=detailreq ,callback=self.detail_page)

    @config(priority=2)
    def detail_page(self, response):
        #转换成Json格式的字符串
        resultjsonstr = json.dumps(response.json)
        result = json.loads(resultjsonstr)['result']['detail']['data']['good']
        #将返回的结果保存到MySQL数据库
        return {
            "original_id": result['gid'].encode('utf-8'),
            "project_name": result['name'].encode('utf-8'),
            "project_desc": result['summary'].encode('utf-8'),
            "curr_money":result['saled'].encode('utf-8'),
            "begin_date":time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(float(result['ctime'].encode('utf-8'))))
        }

    def on_result(self,result):
        if not result or not result['original_id']:
            return
        sql = SQL()
        sql.insert('t_dream_xm_project',**result)  

注:数据库表名需要和on_ result方法中insert函数首个参数对应(如上例中t_dream_xm_project),表的字段名称和detail_page方法中return返回的字段名称对应

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,383评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,522评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,852评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,621评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,741评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,929评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,076评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,803评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,265评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,582评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,716评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,395评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,039评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,027评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,488评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,612评论 2 350

推荐阅读更多精彩内容