Scrapy爬虫入门实例

在搭建好了Scrapy的开发环境后(如果配置过程中遇到问题,请参考上一篇文章
搭建Scrapy爬虫的开发环境
或者在博客里留言),我们开始演示爬取实例。

我们试图爬取论坛-东京版的主题贴。该网
站需要登录后才能查看帖子附带的大图,适合演示登录过程。

1. 定义item

我们需要保存标题、帖子详情、帖子详情的url、图片列表,所以定义item如下:

class RentItem(scrapy.Item):
    """item类"""

    title = scrapy.Field()          # 标题
    rent_desc = scrapy.Field()      # 描述
    url = scrapy.Field()            # 详情的url
    pic_list = scrapy.Field()       # 图片列表

2. 使用FormRequest模拟登录

首先我们需要分析页面,找到登录的form,以及需要提交的数据(用Fiddler或Firebug分析请求即可),
然后使用Scrapy提供FormRequest.from_response()模拟页面的登录过程,主要代码如下:

# 需要登录,使用FormRequest.from_response模拟登录
    if "id='lsform'" in response.body:
        logging.info("in parse, need to login, url: {0}".format(response.url))
        form_data = {
            "handlekey": "ls",
            "quickforward": "yes",
            "username": "loginname",
            "password": "passwd"
        }
        request = FormRequest.from_response(
                response=response,
                headers=self.headers,
                formxpath="//form[contains(@id, 'lsform')]",
                formdata=form_data,
                callback=self.parse_list
                )
    else:
        logging.info("in parse, NOT need to login, url: {0}"
                     .format(response.url))
        request = Request(url=response.url,
                          headers=self.headers,
                          callback=self.parse_list,
                          )

如果请求的页面需要登录,则通过xpath定位到对应的form,将登录需要的数据作为参数,提交登录,
在callback对应的回调方法里,处理登录成功后的爬取逻辑。

3. 使用XPath提取页面数据

Scrapy使用XPath或CSS表达式分析页面结构,由基于lxml的Selector提取数据。XPath或者CSS都可
以,另外BeautifulSoup
分析HTML/XML文件非常方便,这里采用XPath分析页面,请参考
zvon-XPath 1.0 Tutorial,示例丰富且易
懂,看完这个入门教程,常见的爬取需求基本都能满足。我这里简单解释一下几个重要的点:

  • /表示绝对路径,即匹配从根节点开始,./表示当前路径,//表示匹配任意开始节点;

  • *是通配符,可以匹配任意节点;

  • 在一个节点上使用[],如果是数字n表示匹配第n个element,如果是@表示匹配属性,还可以使用函数,
    比如常用的contains()表示包含,starts-with()表示字符串起始匹配等。

  • 在取节点的值时,text()只是取该节点下的值,而不会取该节点的子节点的值,而.则会取包括子节点
    在内的所有值,比如:

<div>Welcome to <strong>Chengdu</strong></div>

sel.xpath("div/text()")     // Welcome to
sel.xpath("div").xpath("string(.)")     // Welcome to Chengdu

4. 不同的spider使用不同的pipeline

我们可能有很多的spider,不同的spider爬取的数据的结构不一样,对应的存储格式也不尽相同,因此
我们会定义多个pipeline,让不同的spider使用不同的pipeline。

首先我们需要定义一个decorator,表示如果spider的pipeline属性中包含了添加该注解的pipeline,
则执行该pipeline,否则跳过该pipeline:

def check_spider_pipeline(process_item_method):
    """该注解用在pipeline上

    :param process_item_method:
    :return:
    """
    @functools.wraps(process_item_method)
    def wrapper(self, item, spider):

        # message template for debugging
        msg = "{1} {0} pipeline step".format(self.__class__.__name__)

        # if class is in the spider"s pipeline, then use the
        # process_item method normally.
        if self.__class__ in spider.pipeline:
            logging.info(msg.format("executing"))
            return process_item_method(self, item, spider)

        # otherwise, just return the untouched item (skip this step in
        # the pipeline)
        else:
            logging.info(msg.format("skipping"))
            return item

    return wrapper

然后,我们还需要在所有pipeline类的回调方法process_item()上添加该decrator注解:

@check_spider_pipeline
def process_item(self, item, spider):

最后,在spider类中添加一个数组属性pipeline,里面是所有与该spider对应的pipeline,比如:

# 应该交给哪个pipeline去处理
pipeline = set([
    pipelines.RentMySQLPipeline,
])

5. 将爬取的数据保存到mysql

数据存储的逻辑在pipeline中实现,可以使用twisted adbapi以线程池的方式与数据库交互。首
先从setttings中加载mysql配置:

@classmethod
def from_settings(cls, settings):
    """加载mysql配置"""

    dbargs = dict(
        host=settings["MYSQL_HOST"],
        db=settings["MYSQL_DBNAME"],
        user=settings["MYSQL_USER"],
        passwd=settings["MYSQL_PASSWD"],
        charset="utf8",
        use_unicode=True
    )

    dbpool = adbapi.ConnectionPool("MySQLdb", **dbargs)
    return cls(dbpool)

然后在回调方法process_item中使用dbpool保存数据到mysql:

@check_spider_pipeline
def process_item(self, item, spider):
    """pipeline的回调.

    注解用于pipeline与spider之间的对应,只有spider注册了该pipeline,pipeline才
    会被执行
    """

    # run db query in the thread pool,在独立的线程中执行
    deferred = self.dbpool.runInteraction(self._do_upsert, item, spider)
    deferred.addErrback(self._handle_error, item, spider)
    # 当_do_upsert方法执行完毕,执行以下回调
    deferred.addCallback(self._get_id_by_guid)

    # at the end, return the item in case of success or failure
    # deferred.addBoth(lambda _: item)
    # return the deferred instead the item. This makes the engine to
    # process next item (according to CONCURRENT_ITEMS setting) after this
    # operation (deferred) has finished.
    time.sleep(10)
    return deferred

6. 将图片保存到七牛云

查看七牛的python接口即可,这里要说明的是,上传图片的时候,不要使用BucketManager的
bucket.fetch()接口,因为经常上传失败,建议使用put_data()接口,比如:

def upload(self, file_data, key):
    """通过二进制流上传文件

    :param file_data:   二进制数据
    :param key:         key
    :return:
    """
    try:
        token = self.auth.upload_token(QINIU_DEFAULT_BUCKET)
        ret, info = put_data(token, key, file_data)
    except Exception as e:
        logging.error("upload error, key: {0}, exception: {1}"
                      .format(key, e))

    if info.status_code == 200:
        logging.info("upload data to qiniu ok, key: {0}".format(key))
        return True
    else:
        logging.error("upload data to qiniu error, key: {0}".format(key))
        return False

7. 项目部署

部署可以使用scrapydscrapyd-client
首先安装:

$ pip install scrapyd
$ pip install scrapyd-client

启动scrapyd:

$ sudo scrapyd &

修改部署的配置文件scrapy.cfg:

[settings]
default = scrapy_start.settings

[deploy:dev]
url = http://localhost:6800/
project = scrapy_start

其中dev表示target,scrapy_start表示project,部署即可:

$ scrapyd-deploy dev -p scrapy_start

ok,这篇入门实例的重点就这么多,项目的源码在gitlab

参考

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

推荐阅读更多精彩内容

  • scrapy学习笔记(有示例版) 我的博客 scrapy学习笔记1.使用scrapy1.1创建工程1.2创建爬虫模...
    陈思煜阅读 12,688评论 4 46
  • scrapy是python最有名的爬虫框架之一,可以很方便的进行web抓取,并且提供了很强的定制型,这里记录简单学...
    bomo阅读 2,107评论 1 11
  • Python版本管理:pyenv和pyenv-virtualenvScrapy爬虫入门教程一 安装和基本使用Scr...
    inke阅读 35,270评论 7 93
  • 如果你已经是为人父母了,你想的最多的是什么? 生活中,我们经常可以听到身边的朋友说自己的小孩怎么让他们操心,这样那...
    雨中风筝阅读 628评论 1 2
  • 感恩的心 营养是什么?就是让生命可以健康成长的那份需要。当然,它的形式很多很多。 前些天,走在我们所处小城市的街道...
    语文行者阅读 585评论 1 12