搭建 PyCharm + Django + Scrapy 项目调试环境

Last Updated: 2016-09-24

花了点时间整了一个Django + Scrapy的混合项目,不算完美,但好在可以用,最终的效果是:

  • 可以用PyCharm调试Scrapy
  • 可以在Scrapy中使用Django ORM和Django提供的其他一系列功能
  • 可以在Django View中直接运行爬虫

创建Django项目

跟平常一样,用django-admin startproject your_project,并按你自己需要使用django-admin startapp your_app创建app,这里不再赘述。

整合Scrapy项目结构

Scrapy跟Django类似,可以用命令行scrapy startproject your_project来创建一个新项目,但因为上一步的缘故,已经有同名的your_project目录存在,scrapy startproject就无法完成了。好在Scrapy项目结构不复杂,新建一个其他名字的Scrapy项目,然后把里面的文件搬到your_project中即可。

整合后的目录结构如下:

如果你熟悉Scarpy项目结构的话,应该就能一眼认出红框里面的那些文件了。
为了跟Django项目中的文件以示区分,我把Scrapy的piplines、items、settings文件都加上了scrapy前缀。
改名后,别忘记相应地修改scrapy.cfg、scrapy_settings.py中的内容。
另外眼尖的你一定发现scrapy.cfg下面有个scrapy_main.py,这个文件是我自己创建的,目的是作为PyCharm调试Scrapy项目的启动脚本,下文会详述。

创建Scrapy项目Configuration

Django项目用PyCharm打开后,PyCharm自动创建了针对Django的Configuration,所以可以直接调试Django。

Scrapy提供了CrawlerRunnerCrawlerProcess这两个API来执行爬虫。
CrawlerProcess代码写起来简单,但无法直接在Django View中调用,会报异常:ValueError: signal only works in main thread
CrawlerRunner据说可以在Django View中调用,但写起来较繁琐,我还没时间去深究。
如果CrawlerRunner方式调用爬虫搞掂后,直接用Django的Configuration应该就能调试爬虫代码了。

既然CrawlerRunnerCrawlerProcess目前都用不了,暂时就采用曲线救国方案:
在Django View中调用commands.getstatusoutput('scrapy crawl spider_name')来执行爬虫。简单粗暴,适合我这种粗人,但随之带来的问题是:由于新起了一个进程执行爬虫,Scrapy相关代码就无法被PyCharm调试了。

为了让PyCharm能够调试爬虫代码,新建一个针对Scrapy的Configuration。
首先准备一个启动脚本,scrapy_main.py文件登场,内容如下:

# -*- coding: utf-8 -*-
from scrapy.cmdline import execute

execute("scrapy crawl weixin -a query=funny".split())

以上代码执行了一个名为weixin的爬虫,并带上了参数query=funny,这个参数会传递到名为weixin的Spider类的构造函数中以供使用。

启动脚本准备好后,还需要修改一下scrapy_settings.py文件,使得Scrapy中可以使用Django的功能。
在scrapy_settings.py中,添加如下代码:

import sys
import os
import django

# setup django environment, so we can use django ORM in scrapy
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
os.environ['DJANGO_SETTINGS_MODULE'] = 'spider_demo.settings' # <-- your django settings module
django.setup()

准备就绪,下面创建配置文件,点击Edit Configurations:

创建一个新配置:

给配置起个名,选择刚才新建的scrapy_main.py作为启动脚本:

现在有两个配置文件:

开始调试

激活Scrapy配置文件,在爬虫的parse方法中放个断点,开始debug:

断点进来了:

打开Evaluate Expression小窗口,在这里可以输入Python表达式来检视结果:

按需inspect_reponse

在表达式窗口调用inspect_reponse看看:

在命令行下输入view(response),调用浏览器打开临时html,以可视化方式测试xpath:

在Django View中直接运行爬虫

方案很多,看你需求而定,像上面提到的,我使用了commands.getstatusoutput('scrapy crawl spider_name'),以阻塞方式执行爬虫,可以得到爬虫的stdout输出。但这种做法只能在demo中用用,要不然爬虫运行时间过长,request分分钟timeout。
如果要以异步方式执行爬虫,可以用subprocess.Popen('scrapy crawl spider_name'.split()),这样不至于阻塞Django View。
还有一种方案,貌似是官方推荐的,使用scrapyd,它是一个爬虫调度器,把爬虫任务发给它,它会负责运行爬虫。

当然,以上的各种方法都没办法让PyCharm同时调试Django和爬虫代码,后来我试验了一下上文提到的CrawlerRunner,经过测试,可行倒是可行,但特么爬虫代码只能跑一次,先看一下代码:

# -*- coding: utf-8 -*-
from twisted.internet import reactor
import scrapy
from scrapy.crawler import CrawlerRunner, CrawlerProcess
from scrapy.utils.project import get_project_settings
from scrapy.utils.log import configure_logging


def crawl(spider_name, *args, **kwargs):
    settings = get_project_settings()
    configure_logging(settings=settings)
    runner = CrawlerRunner(settings=get_project_settings())

    d = runner.crawl(spider_name, *args, **kwargs)
    d.addBoth(lambda _: reactor.stop())
    reactor.run(installSignalHandlers=0)  # the script will block here until the crawling is finished

reactor.run会一直阻塞,直到爬虫结束后,reactor.stop()被调用,reactor.run才得以返回。
但是第二次跑这段代码时,reactor.run会抛异常:ReactorNotRestartable,因为reactor不能被第二次run。


2016-09-24更新

功夫不负有心人,后来在celery worker中执行爬虫时再一次遇到ReactorNotRestartable的问题,这回找到了答案:http://stackoverflow.com/questions/22116493/run-a-scrapy-spider-in-a-celery-task

大致代码如下:

from billiard.process import Process
from scrapy.utils.project import get_project_settings
from scrapy.crawler import CrawlerProcess
from django.db import connection

class BilliardCrawlProcess(Process):
    def __init__(self, group=None, target=None, name=None, args=(), kwargs={}, daemon=None, **_kw):
        super(BilliardCrawlProcess, self).__init__(group, target, name, args, kwargs, daemon, **_kw)

    def run(self):
        settings = get_project_settings()
        process = CrawlerProcess(settings)

        process.crawl('your_spider_name',
                      your_arg_1='some arg')
        process.start()


def crawl():
    crawl_process = BilliardCrawlProcess()
    crawl_process.start()
    crawl_process.join()  # blocks here until scrapy finished
    connection.close()  # NOTE

上面代码中需要注意的地方:

  • 如果你项目中用了celery的话,billiard无需额外安装,如果安装的话,不要装最新版的,因为celery用的是旧版本,跟最新版不兼容
  • 为何在crawl_process.join()后面要跟一句connection.close()?因为我实测下来,如果不显式关掉默认mysql连接的话,后续的数据库操作会引发mysql connection has gone away异常。原因不太了解,可能是因为billiard子进程结束后,把django的默认数据库链接给关掉了,然而当前进程中还不知道连接已经over,所以显式关闭一下,之后再用到数据库时会自动重连。

搭建 PyCharm + Django + Scrapy 项目调试环境》有4个想法

  1. 老鱼儿您好,这个项目在您的GitHub中没有找到,我现在也想用Django+scrapy搭建一个混合项目,不知道您能不能分享一下您的源码?如果可以请您发到我的邮箱里:QQ邮箱1428941524@qq.com,感激不尽

  2. 老师,您好,我目前也正做一个能在django 视图中启动scrapy爬虫项目的程序,我目前甚至不知道您2016-09-24更新的那段代码应该在哪里写。希望有机会能看一下您的源码,
    531459051@qq.com,感谢!

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.