Scrapy

scrapy是一个python框架,可以爬取网站数据。
官方文档:http://scrapy-chs.readthedocs.io/zh_CN/0.24/intro/overview.html

具体网上很多Scrapy的讲解,可自行百度,官方文档也非常详细,安装流程可直接查看官方文档,下面只简单写下流程

Demo

1.创建工程

scrapy startproject movie

2.创建爬虫

cd movie
scrapy genspider imovhub imovhub.com
//第一个imovhub是爬虫名,第二个是对应要爬的域名

3.创建完后目录为下

imovhub.py
主要文件,里面的parse方法可以解析爬到的response数据。
items.py
里面定义了数据对应的Model。
pipelines.py
用来处理数据的管道,一般做数据保存等。
settings.py
配置文件,配置递归层数,并发数,延迟下载等。
middlewares.py
中间件,里面有两个类,一个spider middlewares,处理spider的response和items及requests,一个Downloader middlewars,处理downloader传递给引擎的response。

4.开始编写爬虫

首先看settings.py里,有一项比较重要的

# Obey robots.txt rules
ROBOTSTXT_OBEY = True

这个是用来控制是否遵守要爬的网站里的robots.txt协议的。比如百度的:http://www.baidu.com/robots.txt
为True的时候,如果我们编写的爬虫不在协议里,那么scrapy会自己停止,所以我们一般设置为flase。

接下来看items.py
我们在里面添加我们数据model的成员变量就行

class MovieItem(scrapy.Item):
    # define the fields for your item here like:
    name  = scrapy.Field()
    title  = scrapy.Field()

然后是pipelines.py
我们在这里处理数据序列化等

class MoviePipeline(object):
    def process_item(self, item, spider):
        with open('moive.txt','a') as fp:
            fp.write(item['name'].encode("utf8") +'\n')

上面的代码大概意思就是打开或生成movie.txt文件,然后再里面写入我们item对象里name的值

最后是spiders目录里的imovhub.py文件,这个文件名是之前创建工程时候生成的,不同工程名都不一样,我们在这文件里,编写解析爬虫爬到的数据。

import scrapy
from movie.items import MovieItem #导入我们的Item对象

class ImovhubSpider(scrapy.Spider):
    name = 'imovhub'
    allowed_domains = ['imovhub.com']
    start_urls = ['http://imovhub.com/']

    def parse(self, response):
        movies = response.xpath('//div[@class="moive"]')
        for each_movie in movies:
            item = MovieItem()
            item['name'] = each_movie.xpath('./h5/a/@title').extract()[0]
            yield item

parse方法里处理解析,参数response其实就是爬虫爬到的html文件数据,我们通过解析html里的xml数据拿到我们想要的东西,xpath方法是用来获取xml数据的。

打开我们要爬的网站,进入浏览器的调试模式,查看网页的xml结构

电影封面对应的节点是div class=”moive”,解析这个节点就能获取movies列表,然后再for循环遍历moives列表,找到子节点,找到电影的标题,获取电影名称。

1.缘由

最近在修复项目中的遗漏的Crash问题时,发现一个比较隐晦的队列同步死锁问题,记录了下整个发现过程。

2.经过

首先查看友盟统计上的Crash

发现基本都是signal问题,一开始以为是内存泄漏引起的野指针,解决了大部分内存泄漏后,发现Crash问题并没有改善,于是继续排查。

选择一个后查看堆栈:(对项目名相关做了些涂抹处理)

通过dSYM文件,对堆栈进行符号化:

发现是[FMDatabaseQueue  inDatabase]后抛出异常了,于是开始在FMDB的github上查找类型问题,在issues上搜索发现了类似的奔溃,https://github.com/ccgus/fmdb/issues/613,里面有人解答说的是 inDatabase方法嵌套使用造成死锁了,查看[FMDatabaseQueue  inDatabase]源码,内部是一个同步queue的操作:

在同步操作前FMDB也有相应的断言判断代码当前是否在同一个queue:

看来确定是这个导致的Crash,仔细查看业务逻辑代码后,发现业务代码中并没有比较明显的[FMDatabaseQueue  inDatabase]嵌套调用,到底是怎么出现的呢?于是重新查看Crash堆栈,发现两次[FMDatabaseQueue  inDatabase]中间有一段NSNotification的转发:

于是开始写伪代码复现,逻辑如下:

可以看到确实Crash了,奔溃的堆栈和友盟统计上收集到的堆栈基本类似,可以确定就是这个问题了。

3.分析

大致问题是因为串行队列同步嵌套问题,这里虽然创建了一个串行队列_queue,但是由于是在主线程中进行dispatch_sync操作,

dispatch_sync(_queue, ^{
    [self postNotification:@"TestNotification"];
});

- (void)postNotification:(NSString *)notification的实现是把通知处理函数转发到主线程的封装,在这里实际上[self postNotification:@"TestNotification"]是在主线程中运行,当不是在主队列,而内部使用[NSThread isMainThread]来判断是否异步到Main队列的,且- (void)handleNotification:(NSNotification *)notification所在的线程和post时候是一致的,从而导致了嵌套同步队列。
简化去掉Notification相关代码后等同于:

dispatch_sync(_queue, ^{
    dispatch_sync(_queue, ^{
        NSLog(@"");
    });
});

这样就造成_queue同步死锁了。

正确做法应是把[NSThread isMainThread]改为判断是否主队列

这样由于中间经过了async到主队列,后续就算再sync到目标队列也能避免这个问题。

4.总结

这个问题总共有几个知识点:

1.NSNotification的处理函数所在的线程与Post Notification时的线程一致,官方文档也有相关说明。

2.dispatch_sync后代码块所在的线程和进入dispatch_sync时的线程一致。

3.dispatch_get_main_queue()所在线程一定在MainThread,但MainThread所在队列不一定在dispatch_get_main_queue()

其实这个问题只是个比较常见的队列同步死锁问题,但因为中间经过Notification转发,导致代码不好CodeReview,从而在开发阶段没发现问题,这次分析总结主要也是希望在类似问题上能给大家提供到一些帮助。