網頁爬蟲--scrapy進階


本篇將談一些scrapy的進階內容,幫助大家能更熟悉這個框架。


1. 站點選取

現在的大網站基本除了pc端都會有移動端,所以需要先確定爬哪個。

比如爬新浪微博,有以下幾個選擇:

  1. www.weibo.com,主站
  2. www.weibo.cn,簡化版
  3. m.weibo.cn,移動版

上面三個中,主站的微博數據是動態加載的,意味着光看源碼是看不到數據的,想爬的話要么搞清楚其api訪問情況,要么模擬js,那樣的話花的力氣可能就有點多了。weibo.cn是一個簡化版,數據能直接從網頁源代碼中解析出來,但使用正則或xpath取網頁中的元素仍然是無聊且費時的,更不用說偶爾的頁面結構錯誤更讓人抓狂。 相比之下,移動版的爬蟲最好寫,因為移動版能直接拿到json格式的數據。

一般來說,有移動版的網站優先爬移動版,會節省很多力氣。


2. 模擬登錄

現在需要登錄才能正常瀏覽的網站的越來越多了,對爬蟲也越來越不友好...所以模擬登錄在不少場合都是必不可少的。

首先,最簡單的模擬登錄是只有用戶名密碼的登錄。這時候只需要在發送第一個請求時加上登錄表單數據即可:

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

如果不知道登錄頁面是哪一個的話,也可以在返回第一個請求后登錄:

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

為了保持登錄,注意cookie是不能關閉的(默認情況是開着的,可以在settings.py中設置)。

如果需要驗證碼的話,網上有一些提取分析驗證碼圖片的包,可以提取出來然后手動輸入驗證碼。

上面只是一些簡單的登錄情況,如果驗證碼很變態(比如需要鼠標滑動)或者登錄過程很復雜,需要各種加密(比如新浪微博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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM