在搭建好了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 tosel.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_pipelinedef process_item(self, item, spider):
最后,在spider类中添加一个数组属性pipeline
,里面是所有与该spider对应的pipeline,比如:
# 应该交给哪个pipeline去处理pipeline = set([ pipelines.RentMySQLPipeline, ])
5. 将爬取的数据保存到mysql
数据存储的逻辑在pipeline中实现,可以使用twisted adbapi
以线程池的方式与数据库交互。首
先从setttings中加载mysql配置:
@classmethoddef 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_pipelinedef 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. 项目部署
部署可以使用scrapyd和scrapyd-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。
参考
作者:nkcoder
链接:https://www.jianshu.com/p/8fc69b2e7b21