网页爬虫--scrapy进阶

本篇将谈一些scrapy的进阶内容,帮助你们能更熟悉这个框架。css


1. 站点选取

如今的大网站基本除了pc端都会有移动端,因此须要先肯定爬哪一个。html

好比爬新浪微博,有如下几个选择:python

  1. www.weibo.com,主站
  2. www.weibo.cn,简化版
  3. m.weibo.cn,移动版

上面三个中,主站的微博数据是动态加载的,意味着光看源码是看不到数据的,想爬的话要么搞清楚其api访问状况,要么模拟js,那样的话花的力气可能就有点多了。weibo.cn是一个简化版,数据能直接从网页源代码中解析出来,但使用正则或xpath取网页中的元素仍然是无聊且费时的,更不用说偶尔的页面结构错误更让人抓狂。 相比之下,移动版的爬虫最好写,由于移动版能直接拿到json格式的数据。mysql

通常来讲,有移动版的网站优先爬移动版,会节省不少力气。git


2. 模拟登陆

如今须要登陆才能正常浏览的网站的愈来愈多了,对爬虫也愈来愈不友好...因此模拟登陆在很多场合都是必不可少的。github

首先,最简单的模拟登陆是只有用户名密码的登陆。这时候只须要在发送第一个请求时加上登陆表单数据便可:redis

def start_requests(self):
    return scrapy.FormRequest(
            formdata={'username': '***', 'password': '***'},
            callback=self.after_login
        )

若是不知道登陆页面是哪个的话,也能够在返回第一个请求后登陆:sql

def parse(self, response):
    return scrapy.FormRequest.from_response(
            response,
            formdata={'username': '***', 'password': '***'},
            callback=self.after_login
        )

为了保持登陆,注意cookie是不能关闭的(默认状况是开着的,能够在settings.py中设置)。mongodb

若是须要验证码的话,网上有一些提取分析验证码图片的包,能够提取出来而后手动输入验证码。chrome

上面只是一些简单的登陆状况,若是验证码很变态(好比须要鼠标滑动)或者登陆过程很复杂,须要各类加密(好比新浪微博pc端的登录)的话,模拟登陆确实是个很让人头大的问题。这时候有另外一个通用办法,那就是cookie模拟登陆。网站想知道咱们的登陆状态,都是经过cookie来确认的,因此咱们只须要在每次request的时候都附带上cookie便可实现已登陆的效果。

那么,如何得到cookie呢?有chrome的能够F12打开Network界面,这时候人工网页登陆,便可在headers中看到cookie。获得cookie后,只须要在request中加入本身的cookie便可。

self.cookie = {"_T_WM": self.cookie_T_WM,
                ...
                "SSOLoginState": self.cookie_SSOLoginState}

return Request(url, cookies=self.cookie, callback=self.parse_page)

3. 网页解析

通常来讲,使用xpath和css已经能够应付全部的html源码了,剩下的只是耐心加细心...要是有心的话也能够Item Loaders,方便后期的维护,下面摘自官方文档:

def parse(self, response):
    l = ItemLoader(item=Product(), response=response)
    l.add_xpath('name', '//div[@class="product_name"]')
    l.add_xpath('name', '//div[@class="product_title"]')
    l.add_xpath('price', '//p[@id="price"]')
    l.add_css('stock', 'p#stock]')
    l.add_value('last_updated', 'today')
    return l.load_item()

值得一提的是若是获取的是json格式的数据,可使用python自带的json库来解析成一个字典或列表:

data = json.loads(response.body)

4. 数据存储

可使用twisted提供的数据库库来维护一个链接池:

class CnblogPipeline(object):
    def __init__(self):
        self.dbpool = adbapi.ConnectionPool('MySQLdb',
                                            host='localhost',
                                            db='cnblog',
                                            user='root',
                                            passwd='root',
                                            cursorclass=MySQLdb.cursors.DictCursor,
                                            charset='utf8',
                                            use_unicode=True)

    def process_item(self, item, spider):
        self.dbpool.runInteraction(self.cnblog_insert, item)
        return item
    
    def cnblog_insert(self, cur, item):
        try:
            cur.execute('insert into ***')
        exception MySQLdb.Error, e:
            logging.info("cnblog_insert:%s" % str(e))

若是爬的是社交网站这种有着树型结构关系的网站的话,mongodb其符合人的思惟的存储方式让其成为首选。

若是使用mysql的话记得将innodb_flush_log_at_trx_commit这个参数置为0(每一秒读写一次数据和log),能够大大提升读写速度。


5. scrapy小知识点

  • Request传递消息。在Request中加入meta,便可将meta传递给response。
Request(url, meta={'how': 'ok'}, callback=self.parse_page)

def parse_page(self, response):
    print response.meta['how']
  • CrawlSpider。都知道在写本身的spider的时候须要继承scrapy的spider,除了scrapy.Spider外,scrapy还提供了好几种spider,其中CrawlSpider算是比较经常使用的。CrawlSpider的优点在于能够用rules方便地规定新的url的样子,即经过正则匹配来约束url。而且不须要本身生成新的url,CrawlSpider会本身寻找源码中全部符合要求的新url的。另外,rules的回调方法名字最好不要叫parse。
class CnblogSpider(CrawlSpider):
    name = "cnblog_spider" 
    allowed_domain = ["cnblog.com"]

    start_urls = ["http://www.cnblogs.com/rubinorth"]

    rules = [
        Rule(LinkExtractor(allow=r"http://www.cnblogs.com/rubinorth/p/\d+\.html"),
                            callback="parse_page", follow=True)
    ]
  • parse中既返回item又生成新的request。平时在parse中return item便可返回item,return request则生成新的request请求。若是咱们将return换为yield的话便可既返回item又生成新的request。注意一旦使用了yield,那么parse方法中就不能有return了。
def parse_page(self, response):
    item = CnblogItem()
    ****
    yield item
    yield Request(new_url, callback=self.parse_page)
  • 每一个spider不一样设置。在spider中加入custom_settings便可覆盖settings.py中相应的设置,这样的话在settings.py中只须要放一些公用的设置就好了。最经常使用的就是设置每一个spider的pipeline。
custom_settings={
    'ITEM_PIPELINES' : {
        'cnblog_project.pipelines.CnblogPipeline': 300,
    }
}
  • 读取本身的设置。无论在spider中仍是pipeline中,均可以写from_crawler这个方法(注意spider中参数数目不一样)。此方法在初始化阶段由scrapy本身调用,其最大的做用就是从settings.py读取本身的设置了。下面的代码从settings.py中读取了MONGO_URI。
class MongoPipeline(object):

    def __init__(self, mongo_uri, mongo_db):
        self.mongo_uri = mongo_uri
        
    @classmethod
    def from_crawler(cls, crawler):
        return cls(
            mongo_uri=crawler.settings.get('MONGO_URI')
        )
  • 顺序。settings.py中middleware和pipeline设置的时候须要在后面跟上一个数字,而这个数字的含义就是调用的顺序,数字越小越早调用。
DOWNLOADER_MIDDLEWARES = {
    'cnblog_project.my_mv.middleware.UserAgentMiddleware': 543,
    'cnblog_project.my_mv.middleware.ProxyMiddleware':544,
}
  • pipeline中spider.name的应用。pipeline中的process_item中能够根据spider.name来对不一样的item进行不一样的处理。
def process_item(self, item, spider):
    if spider.name == 'a':
        ****
    if spider.name == 'b':
        ****
  • 多个pipeline处理一个item。pipeline中的process_item方法必须返回一个item或者raise一个DropItem的异常,若是返回item的话这个item将会被以后的pipeline接收到。
def process_item(self, item, spider):
    return item
  • 以后可能会有添加。

其实这些在scrapy的官方文档都有说起,在此只是总结一些经常使用的知识点。若想更深刻地了解scrapy,定然是阅读其官方文档最好了。:)


6. scrapy_redis简介

scrapy_redis是一个分布式爬虫的解决方案。其思想为多个爬虫共用一个爬取队列,此队列使用redis存储,由于redis是一个内存数据库,因此速度上与单机的队列相差不大。

那么,scrapy_redis到底对scrapy作了什么修改,达到分布式的目的呢?

查看github上面的源码,能够发现其功能性代码集中在scheduler.py,dupefilter.py和queue.py中,分别是调度器(部分功能),去重,以及redis队列的实现。scrapy_redis就是将这些功能代替了scrapy本来的功能(并无修改scrapy源码,只须要在settings.py中进行设置便可),从而达到了分布式的效果。


参考资料

scrapy官中文档

转载请注明出处:http://www.cnblogs.com/rubinorth/

相关文章
相关标签/搜索