最近所學——爬蟲心得以及學習體會(本人的第一篇博客)


  由於論文的關系,要大量的微博文本數據,在網上查了很多,沒有可以直接用的現成數據,因此就入了爬蟲的坑,通過同學介紹看了《精通Python網絡爬蟲》的書,也結合一些大牛的博客,如願獲得了自己想要的數據。在這主要記錄一下自己學習這本書的心得、自己爬取微博數據的過程以及中途遇到的一些問題。

  關於精通Python網絡爬蟲這本書,對於一個從來沒解除過爬蟲的我來說,這可以算是一個非常好入門的書籍了,只要稍微有點Python的基礎知識就可以很好學習這本書了。通過學習這本書,發現其實對於爬取web數據來說,最重要的就是如何獲取大量的數據以及對於想要的數據的提取了,這里就不得不說到對於網頁地址的分析以及正則表達式了,以及如何模擬登陸還有關於爬蟲的Scrapy框架的知識。這本書的一些基礎內容就不說了(包括爬蟲的原理以及Python應用於爬蟲的比如Urllib這樣的庫等等),直接進入我想要說的一些爬蟲的重點,這些不算是難點,爬蟲整體來說還是比較簡單的。

  首先,說一下我對正則表達式的理解,通過python中的一些函數,可以將網頁的源代碼獲取下來,但是如何從這些源代碼中得到自己想要的數據呢(比如圖片或者是一些文章標題以及微博內容),這就用到了正則表達式了,可能對於很多人來說正則表達式並不是個問題,因為正則表達式只要稍微用幾次就可以很好的掌握了,但是對於剛開始的我,對於正則表達式確實是有點煩的,起初就是覺得有點麻煩,也不知道怎么寫出正好可以匹配的正則來提取出想要的內容,通過學習之后,發現這是一個很靈活的東西,只要可以找到唯一識別的標識或字符串,就可以很容易的找到自己想要的東西,而且正則的編寫也沒有一個固定的格式,只要能匹配到自己想要的東西就是可以的。關於正則表達式的基礎知識,我主要想說幾個用的比較多也比較好用的:(1)“.”是用來匹配除換行符以外的任意字符;(2)“*”是用來匹配0次、1次或多次前面的原子;(3)“?”是用來匹配0次或1次前面的原子;(4)第4個就要說前面三個的組合了,也就是“.*?”了,這個就是懶惰模式了,不加問好就是貪婪模式,它們兩個的區別是,懶惰模式采用的是就近匹配原則,可以讓匹配結果更加的精確,二貪婪模式就是盡可能多地匹配。關於正則,我就不舉例子了,只要能唯一匹配到自己想要的字符串,那就是好的正則表達式,不過要注意的是,正則表達式中有時候會加入“\”轉義字符;還有一個要注意的是,在正則表達式中(),兩個圓括號就代表了最終匹配的結果是圓括號里的內容,而不是整個正則表達式所匹配的內容,除非兩個圓括號前面都有轉義字符。除了正則表達式,還有一些獲取網頁源代碼中自己想要的標簽的方法,就是xpath表達式等。目前我也只會使用正則和xpath。

  接下來,說一下網頁地址的分析,對於網上大量的數據,比如微博或者京東上的商品圖片等等,他們都不是一下子囤積到一個頁面上的,可以通過翻頁操作查看到更多的內容,對於爬蟲來說呢,為了高效簡便的獲取數據,就要使得爬蟲可以大量自動的爬取想要的內容,這其中就設計到對於網頁地址的分析了, 因為對於網頁上的翻頁操作,其實都是可以通過網址上的參數來反映出來的,首先打開想要獲取的數據的首頁https://list.jd.com/list.html?cat=9987,653,655,比如京東上的手機分類,為了自動獲取大量的手機圖片或數據,就要對頁面進行分析,這時候點擊網頁最下面的“下一頁”,這時觀察網頁地址為

https://list.jd.com/list.html?cat=9987,653,655&page=2&sort=sort_rank_asc&trans=1&JL=6_0_0&ms=6#J_main,可以發現和首頁的網址相比,多了很多參數page=2&sort=sort_rank_asc&trans=1&JL=6_0_0&ms=6#J_main,對於多的這些參數,主要的是其中的page=2,這個就是控制網頁翻頁的參數page,不過對於不同的網站,控制翻頁的參數不是都是page,有的可能是id,有的可能是pagenum等等。通過這樣類似的分析就可以控制讓爬蟲自動按照順序去爬取想要的數據。不過有的網站的頁面分析並不像沒這么簡單的分析就可以得到翻頁的控制參數,需要運用到一些抓包分析軟件或者利用瀏覽器自帶的工具(F12)等,這里我解除到了Fiddler,這是一個抓包分析軟件,它可以詳細地對HTTP請求進行分析,還能模擬對應的HTTP請求。通過學習這個軟件,再難的網頁翻頁的控制參數都可以得到,這里我主要說一下關於微博數據的翻頁,首先我鋪墊一下,因為我是要獲取微博“發現”下的熱門分類的內容,對於一個分類,我要分析它的頁面構成,微博頁面不同於上面所說的京東商城,它的微博內容起初會顯示15條,然后往下拉,后出現“正在加載”的字眼,根據鼠標往下拖動頁面會自己加載,其實這個加載的過程就好比點擊了“下一頁”,由於微博內容全都顯示在同一網頁上,因此網址不會發生改變,這些網址的變化,只能通過抓包軟件或者瀏覽器自帶的工具來查看,通過Fiddler,我發現了其中的不同,對於起初的15條內容,他的真實數據網址是

http://d.weibo.com/p/aj/v6/mblog/mbloglist?ajwvr=6&domain=102803_ctg1_1388_-_ctg1_1388&from=faxian_hot&mod=fenlei&pagebar=0&tab=home&current_page=1&pre_page=1&page=1&pl_name=Pl_Core_NewMixFeed__3&id=102803_ctg1_1388_-_ctg1_1388&script_uri=/102803_ctg1_1388_-_ctg1_1388&feed_type=1&domain_op=102803_ctg1_1388_-_ctg1_1388&__rnd=1504661045801 HTTP/1.1,

而加載一次的網址是

http://d.weibo.com/p/aj/v6/mblog/mbloglist?ajwvr=6&domain=102803_ctg1_1388_-_ctg1_1388&from=faxian_hot&mod=fenlei&pagebar=1&tab=home&current_page=2&pre_page=1&page=1&pl_name=Pl_Core_NewMixFeed__3&id=102803_ctg1_1388_-_ctg1_1388&script_uri=/102803_ctg1_1388_-_ctg1_1388&feed_type=1&domain_op=102803_ctg1_1388_-_ctg1_1388&__rnd=1504661183573 HTTP/1.1

其中的102803_ctg1_1388_-_ctg1_1388代表的是一個類別,這里代表的是體育類,因此這個不重要,對於不同的類,只要將102803_ctg1_1388_-_ctg1_1388里后面兩個數字替換一下即可。觀察這兩個網址,發現里面最主要的是四個參數,也就是pagebar、current_page、pre_page、page,其他的對頁面都不會有什么影響,就不用管它們。對於這四個參數,再觀察可以發現只有pagebar和current_page發生了變化,我剛開始分析,只加載了三四頁,就得出結論微博數據的頁面翻頁由pagebar和current_page來控制的,可是當我爬取數據的時候,隨着pagebar和current_page越來越大,發現頁面獲取的內容就是空的,說明是頁面翻頁的分析是不對的。之后我又通過Fiddler分析了24次加載的過程,發現在微博頁面里,六頁一個周期,沒加載夠六次,需要手動點擊顯示更多來顯示更多的微博內容,分析了24次的網頁地址結果我就不放上來了,有興趣的可以通過抓包軟件自己去看看,我直接把分析的結果放上來。加載24頁后,我發現了頁面的翻頁是這四個參數來控制,而且還是有規律的,current_page是從1開始,沒加載一頁,就加1;而pagebar是從0開始,加載到4,0-4也就是5個頁面,第6個頁面pagebar就不在網頁地址中了,然后下一次加載pagebar就繼續從0開始,是一個循環,6是周期;然后就是pre_page,這個我理解的是當前大頁碼的意思,它的值,起初的六個頁面pre_page都是1,第二次的六個頁面它的值都是2,因此我之前分析了三四次的加載都看不出它的變化也是這個原因;page和pre_page類似,對於起初的六個頁面中的前五個頁面page是1,第六個頁面page變成了2,杜宇第二次的六個頁面中的前五個頁面page是2,第六個頁面就變成了3;通過這些分析,我把微博某一類別微博數據的頁面翻頁理解為由這四個參數來控制,而且還是一個二層頁面的樣子,由pre_page和page來控制大頁面頁碼,由pagebar來控制小頁頁碼,current_page來控制小頁的數量,用這個分析繼續去爬取數據,發現隨着值的增大,獲取的數據又編程了空,發現又分析錯了。這次我又將頁面的加載次數增加到30次,這時候四個參數的變化都和之前的分析一樣,只是加載到第30頁之后,在網站里就沒有“顯示更多”的字眼了,這時候不能加載更多的微博數據了,這時就有點不知道該怎么繼續去分析頁面翻頁的控制參數了,然后我就打開了此時網頁的源代碼來查看,發現了,微博顯示的這一大欄里,源代碼里有個pagenum=1的字眼,這時我推斷這個參數可能是控制參數,這樣子的話,微博的頁面翻頁就是一個三層翻頁過程了,有pagenum控制最大的頁面,這個頁面又分為5個中頁,30個小頁,也就是current_page從1加載到30,然后pagenum就加1了。這次利用這個分析構造出來的微博數據網頁地址來獲取微博數據,發現不會出現空頁面了,因此這個分析就是正確的了。說了這么多關於頁面翻頁分析的東西,其實這個頁面分析並沒有那么難,只是是一個需要自己去細細觀察和要有耐心的工作,針對不同的網站,只要分析出網頁地址翻頁或者關鍵詞的控制參數,就可以自動的爬取數據了。對於關鍵詞的參數控制,也是可以顯示在網頁地址中的,就比如剛才102803_ctg1_1388_-_ctg1_1388代表體育,102803_ctg1_2088_-_ctg1_2088就代表科技了,只要通過觀察網頁地址的變化以及一些經驗就可以輕松的得出了。

  說完正則和網頁地址的分析之后,我要說一下模擬登陸的東西了,這里主要以微博為例,因為對於微博這樣的網站,它的微博瀏覽需要人們登陸了微博之后了,才可以瀏覽更多更多的微博,因為需要取模擬微博的登錄,如果不模擬登陸,就不能獲取得到自己想要的數據頁面。模擬登陸的過程主要就是找到請求網站要發送的數據(即postdata),在給網站發送訪問請求時,帶上需要傳遞的postdata即可模擬網站的登錄,對於微博的模擬登陸是比較難的,這里主要推薦http://blog.csdn.net/together_cz/article/details/70198369這個文章,大家想要獲取微博數據的可以去看一下,對於獲取微博數據,要結合抓包分析軟件如Fiddler來進行分析,分析得到它需要傳遞的postdata,通過它也可以和之前一樣分析出微博數據的真實地址來(微博數據的真實地址應該都是通過js渲染的,目前這個我也不太懂,但是獲取數據只要能找到真實地址即可)。不過對於模擬登錄,還有一些更簡便的方法,那就是應用selenium來實現自動登錄,應用selenium下的webdriver可以輕松加載一個瀏覽器引擎,通過向網站真實登錄地址傳遞用戶名和密碼,還可以傳遞“點擊登錄”的操作,這樣就可以完成網站(如微博)的模擬登陸了,代碼貼到下面供大家參考。

from selenium import webdriver

browser =webdriver.PhantomJS(executable_path="D:/phantomjs-2.1.1-windows/bin/phantomjs.exe")  #加載某個瀏覽器引擎
  

browser.get("http://login.sina.com.cn/sso/login.phpclient=ssologin.js(v1.4.19)")   # 請求微博真實登錄地址
time.sleep(3)
        browser.find_element_by_xpath('//input[@name="username"]').send_keys("你的用戶名")
        browser.find_element_by_xpath('//input[@name="password"]').send_keys("你的密碼")
        browser.find_element_by_xpath('//input[@class="W_btn_a btn_34px"]').click()
time.sleep(3)

其中的find_element_by_xpath方法是通過xpath表達式來找到真實登錄網頁源代碼中輸入用戶名和密碼以及點擊登錄的標簽的。通過這簡單的幾行代碼就可以完成微博的模擬登陸了。

  最后就是要說下scrapy框架了,scrapy框架是一個很簡便的爬蟲框架,里面可以很方便的設置一些避免爬蟲被禁止的操作,比如禁止cookie,使用ip池用戶代理池等,這就是settings.py。還可以對自己想要獲取的數據設置一個存儲容器,這就是scrapy框架的items.py。還可以對獲取的數據進行后續的處理,比如存入數據庫之類的,這就是pipelines.py。還有一個中間件文件middlewares.py,這個文件和scrapy中最重要的爬蟲文件是最重要的兩個文件,對於中間件文件中,我們可以設置一些類,然后在settings文件中啟動這個類,這樣,對於爬蟲文件中每次發送的Request都會經過中間件,這樣可以利用中間件去做一些操作(具體視情況而定,這個我也不是很懂,只是按照自己的理解來說的,還是需要積累更多經驗才能徹底了解),而對於scrapy中的爬蟲文件,這是scrapy中最重要的文件了,我在這里主要說下爬蟲的運行流程,直接貼圖片來展示,scrapy中的爬蟲文件主要是繼承了scrapy.Spider類的,直接放上Spider類的源碼

class Spider(object_ref):
    """Base class for scrapy spiders. All spiders must inherit from this
    class.
    """

    name = None
    custom_settings = None

    def __init__(self, name=None, **kwargs):
        if name is not None:
            self.name = name
        elif not getattr(self, 'name', None):
            raise ValueError("%s must have a name" % type(self).__name__)
        self.__dict__.update(kwargs)
        if not hasattr(self, 'start_urls'):
            self.start_urls = []

    @property
    def logger(self):
        logger = logging.getLogger(self.name)
        return logging.LoggerAdapter(logger, {'spider': self})

    def log(self, message, level=logging.DEBUG, **kw):
        """Log the given message at the given log level

        This helper wraps a log call to the logger within the spider, but you
        can use it directly (e.g. Spider.logger.info('msg')) or use any other
        Python logger too.
        """
        self.logger.log(level, message, **kw)

    @classmethod
    def from_crawler(cls, crawler, *args, **kwargs):
        spider = cls(*args, **kwargs)
        spider._set_crawler(crawler)
        return spider

    def set_crawler(self, crawler):
        warnings.warn("set_crawler is deprecated, instantiate and bound the "
                      "spider to this crawler with from_crawler method "
                      "instead.",
                      category=ScrapyDeprecationWarning, stacklevel=2)
        assert not hasattr(self, 'crawler'), "Spider already bounded to a " \
                                             "crawler"
        self._set_crawler(crawler)

    def _set_crawler(self, crawler):
        self.crawler = crawler
        self.settings = crawler.settings
        crawler.signals.connect(self.close, signals.spider_closed)

    def start_requests(self):
        for url in self.start_urls:
            yield self.make_requests_from_url(url)

    def make_requests_from_url(self, url):
        return Request(url, dont_filter=True)

    def parse(self, response):
        raise NotImplementedError

    @classmethod
    def update_settings(cls, settings):
        settings.setdict(cls.custom_settings or {}, priority='spider')

    @classmethod
    def handles_request(cls, request):
        return url_is_from_spider(request.url, cls)

    @staticmethod
    def close(spider, reason):
        closed = getattr(spider, 'closed', None)
        if callable(closed):
            return closed(reason)

    def __str__(self):
        return "<%s %r at 0x%0x>" % (type(self).__name__, self.name, id(self))

    __repr__ = __str__

爬蟲文件里的類,都會繼承這個類,這個類就是提供最基本的功能。對於其中的參數和方法,具體理解如下(是截取別人所說的):

name:

  這個屬性是字符串變量,是這個類的名稱,代碼會通過它來定位spider,所以它必須唯一,它是spider最重要的屬性。回頭看看源碼中__init__的定義,可以發現這個屬性是可以修改的,如果不喜歡或者有需要重命名spider的name,可以在啟動的時候傳參修改name屬性。

allowed_domains:

  這個屬性是一個列表,里面記載了允許采集的網站的域名,該值如果沒定義或者為空時表示所有的域名都不進行過濾操作。如果url的域名不在這個變量中,那么這個url將不會被處理。不想使用域名過濾功能時可以在settings中注釋掉OffsiteMiddleware, 個人不建議這么做。

start_urls:

  這個屬性是一個列表或者元組,其作用是存放起始urls,相當於這次任務的種子。使用默認模板創建spider時,該值是個元組,創建元組並且只有一個元素時需要在元素后面添加“,”來消除歧義,不然會報錯:“ValueError: Missing scheme in request url: h”。這邊經常有人出錯,為了避免這個錯誤可以根據上一章內容,將模板中start_urls的值設置為列表。

custom_settings:

  這個屬性值是一個字典,存放settings鍵值對,用於覆蓋項目中的settings.py的值,可以做到在一個項目中的不同spider可以有不同的配置。不過這個值要慎用,有些settings的值覆蓋也沒有起作用,eg:“LOG_FILE”。如果想每個spider都有自己的log文件的話就不能這么做。因為日志操作在這個方法執行之前,那么無論怎么改都改不了之前的行為。不過這個問題scrapy研發團隊已經注意到了,相信不久的將來會進行處理的。

crawler:

  這個值從源碼可以看出來自於方法from_crawler()。該值是一個Crawler 實例, 其作用后面的教程會講解,這邊就不細說了。

 settings:

  這個值也是來自於方法from_crawler()。是一個Settings 實例,這個后面也會細說,稍安勿躁哈。

logger:

  顧名思義,記錄日志用的,也是后面講,耐心等候哈。

from_crawler:

  這是一個類方法,scrapy創建spider的時候會調用。調用位置在crawler.py 的類Crawler中,源碼可以自己去看看,就不帶大家看了。這個方法的源碼在上面,我們可以看到,在實例化這個spider以后,這個實例才有的settings和crawler屬性,所以在__init__方法中是沒法訪問這倆屬性的。如果非要在__init__方法中使用相關屬性,那么只能重寫該方法,大家可以嘗試寫寫。

start_requests():

  這個方法必須返回一個可迭代對象,切記!!!!上面就有源碼很簡單,就不細說了。如果想對屬性start_urls做一些操作(增刪改),並希望結果作為種子url去采集網站的時候,可以重寫這個方法來實現。有了這個方法,甚至都不用在代碼中定義start_urls。比如我們想要讀取持久化的url執行采集操作,那么就沒必要轉存進start_urls里面,可以直接請求這些urls。當種子urls需要post請求的話,也需要重寫該方法。

 make_requests_from_url(url):

  這個方法顧名思義,要是還不懂就看看上面的源碼。這里只說一點,因為這里的Request初始化沒有回調方法,就是默認采用parse方法作為回調。另外這里的dont_filter值為True,這個值的作用是該url不會被過濾,至於具體細節請聽下回分解。

 parse(self, response):

  這個方法對於有經驗的同學來說再熟悉不過了,我就簡單的說說。這個方法作為默認回調方法,Request沒有指定回調方法的時候會調用它,這個回調方法和別的回調方法一樣返回值只能是Request, 字典和item對象,或者它們的可迭代對象。

 log(message[, levelcomponent]):

  這個方法是對logger的包裝,看看源碼就好,沒什么什么可說的。

closed(reason):

  當這個spider結束時這個方法會被調用,參數是一個字符串,是結束的原因。這種用法以后會介紹,這里只需記住,想在spider結束時做一些操作時可以寫在這里。

  對於初學者,其中最主要需要查看的start_requests和make_requests_from_url方法了,這兩個方法可以告訴你scrapy框架中爬蟲運行起來時Request和Response是怎樣被傳遞的。對於我現在的理解來說,當啟動scrpay爬蟲后,首先會根據設置來初始化,然后根據爬蟲文件中的start_requests和make_requests_from_url來發送Request,然后Request也會經過中間件處理,最后返回的Response由爬蟲文件中的parse接收,然后對Response進行處理,獲取相應數據。因此可以通過中間件,以及修改start_requests和make_requests_from_url、parse來達到自己想要的要求(或邏輯)。

 

  最后,說下我最近學習爬蟲的心得吧,在獲取頁面源碼到編寫正則提取數據的過程或者是模擬登陸的過程中,都要通過適當的輸出來調式程序,看哪里運行了哪里沒有運行,都可以通過適當的輸出來檢查。然后對於過程中碰到的任何問題,都要很有耐心的去解決,通過查閱瀏覽器或者和他人討論,來解決問題。寫博客只是為了記錄下最近的學習過程,其中有哪里說得不對的,希望看到這篇博客的人見諒,也希望看到這篇博客的大牛們給我分享點經驗和意見。這也是本人的第一篇博客,可能格式上有哪里看的不舒服的,希望可以見諒。希望這篇博客可以幫到你。

 

 

 
       


免責聲明!

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



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