爬蟲實(shí)戰(zhàn)1.4.1 Ajax數(shù)據(jù)采集-微博博客采集

不知道大家有沒有遇到這種情況:當(dāng)我們r(jià)equests發(fā)出請求采集頁面信息的時(shí)候,得到的結(jié)果肯能會(huì)跟在瀏覽器中看到的不一樣,在瀏覽器中看到的數(shù)據(jù),使用requests請求時(shí)可能會(huì)沒有。

1.前言

上面這種情況的原因就是requests獲取的都是靜態(tài)的HTML文檔內(nèi)容,而瀏覽器中看到的頁面,其中的部分?jǐn)?shù)據(jù)可能是JavaScript處理后生成的數(shù)據(jù),這種數(shù)據(jù)也有很多種生成方式:有Ajax加載生成的,也有經(jīng)過JavaScript和一定的計(jì)算方式生成的。
那對于Ajax,這里簡單介紹一下:Ajax是一種異步數(shù)據(jù)加載方式,就是原始的頁面生成之后,開始不會(huì)包含這部分?jǐn)?shù)據(jù),之后會(huì)通過再次向服務(wù)器端請求某個(gè)接口獲取,然后再經(jīng)過一定處理顯示再頁面上。
所以,我們以后遇到這種頁面時(shí),我們直接發(fā)送requests請求是無法獲取到一些數(shù)據(jù)的,這時(shí)候我們就需要找到這部分?jǐn)?shù)據(jù)的源頭:也就是這個(gè)Ajax請求,在進(jìn)行模擬,就可以成功獲取到數(shù)據(jù)了,比如我們最開始實(shí)現(xiàn)的例子:爬蟲開發(fā)實(shí)戰(zhàn)1.1 解決JS加密。沒有看的或者不記得的,可以返回去仔細(xì)的看一下。
這篇主要是通過一個(gè)小例子來了解一下Ajax以及如何去解析采集這類的數(shù)據(jù)。

至于什么是Ajax,如果需要了解其原理的話,可以去W3School上看下幾個(gè)示例
http://www.w3school.com.cn/ajax/ajax_xmlhttprequest_send.asp
或者去崔老師的博客
[Python3網(wǎng)絡(luò)爬蟲開發(fā)實(shí)戰(zhàn)] 6.1-什么是Ajax

2.Ajax分析

我們先去找個(gè)博主。作為吃貨大軍中的一員,果斷去了美食欄目,就拿第一個(gè)博主為例吧,名字也很接地氣?。?a target="_blank">365道菜:https://weibo.com/u/1558473534?refer_flag=1087030701_2975_2023_0&is_hot=1
先看下他的主頁,這不是在打廣告哈。。。

博客頁面

右鍵檢查,彈出開發(fā)者工具界面,我們打開Network選項(xiàng),然后重新刷新頁面,就可以看到目前所有請求返回之后渲染HTML的信息了。
然后我們選擇Ajax相關(guān)的請求,對應(yīng)的請求類型是XHR,這里注意一下:剛開始選擇XHR選項(xiàng)時(shí)是沒有內(nèi)容的,然后鼠標(biāo)滾輪往下滾,直到出現(xiàn)第一條請求為止,見下圖:
Ajax請求

接下來我們→_→,看一下他的一些選項(xiàng),首先Headers
Headers

這里包含了請求的地址,請求的方式是get請求,請求的code是200表示成功,下面是請求頭,返回頭,還有請求的參數(shù),可以說這里包含了一個(gè)請求所有的內(nèi)容了,先不看具體字段的意思。
這時(shí)候一個(gè)請求可能看不出什么,可以繼續(xù)往下滾動(dòng)滾輪,直到最下面,這里出現(xiàn)了分頁, 就先不管了:
底部

我們再看下請求,這里有多出現(xiàn)了一條,下面就根據(jù)這兩條Ajax請求來分析一下:
Ajax請求

剛才已經(jīng)了解了Headers了,下面看下Preview跟Response,兩者都是響應(yīng)的信息,只是Preview是標(biāo)準(zhǔn)的Json格式的,好看一點(diǎn):
Preview響應(yīng)

這里就比較清楚了,返回了三個(gè)參數(shù):code, data, msg,genuine意思我們就可以猜測:data中就是我們
需要的數(shù)據(jù)了,把鼠標(biāo)移到Show more(400KB)上,發(fā)現(xiàn)是一個(gè)HTML的代碼塊,看起來不是很清晰。
可以通過右邊的copy,把這段復(fù)制出來,然后在編輯器中新建一個(gè)html,粘貼到這里面,ctrl+alt+L整潔下代碼,呈現(xiàn)一下,看進(jìn)度條還是挺多內(nèi)容的:
獲取到的data

點(diǎn)擊右上角的google瀏覽器,看一下頁面:
頁面

樣式?jīng)]有渲染出來,但是我們根據(jù)圖片可以在原頁面上找出對應(yīng)的內(nèi)容:
響應(yīng)數(shù)據(jù)第一篇內(nèi)容

響應(yīng)數(shù)據(jù)最后一篇內(nèi)容

大致的數(shù)了一下,總共十五篇的信息。
下面的Ajax請求,響應(yīng)的數(shù)據(jù)跟這個(gè)是一樣的分析方式,這里就不再多說了。

3.Ajax數(shù)據(jù)采集

Ajax的分析已經(jīng)完成了,下面就是開始進(jìn)行采集了,首先先把基本架子寫好:

import requests

class WeiboSpider(object):
    def __init__(self):
        self._headers = {
            'Accept': '*/*',
            'Accept-Encoding': 'gzip, deflate, br',
            'Accept-Language': 'zh-CN,zh;q=0.9',
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive',
            'Content-Type': 'application/x-www-form-urlencoded',
            'Host': 'weibo.com',
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36',
            'X-Requested-With': 'XMLHttpRequest',
        }

    def run(self):
        pass

if __name__ == '__main__':
    wb_spider = WeiboSpider()
    wb_spider.run()

這里注意一點(diǎn):現(xiàn)在微博采集數(shù)據(jù)是要在請求頭中帶上cookie的,所以在self._headers中還要加上cookie這個(gè)屬性。

加cookie屬性

現(xiàn)在來看一下請求參數(shù)是什么,因?yàn)槟壳耙豁撝芯椭挥袃纱蝍jax數(shù)據(jù)加載,所以我們可以看一下他們的共性:

第一次加載

第二次加載

通過對比兩次的請求參數(shù),不難發(fā)現(xiàn),其中的pagebar__rnd兩個(gè)參數(shù)會(huì)有些變化,pagebar這個(gè)比較簡單,就分0, 1。 __rnd參數(shù)發(fā)現(xiàn):這個(gè)就是個(gè)時(shí)間戳,不過python中的時(shí)間戳是10位,而且是小數(shù),這里的是13位,這樣可以自己去測試一下:當(dāng)前時(shí)間的時(shí)間戳再拼接上3位的隨機(jī)數(shù):

    def get_response(self, req_url, params_dict=None):
        if params_dict:
            response = requests.get(req_url, params=params_dict, headers=self._headers)
        else:
            response = requests.get(req_url, headers=self._headers)
        if response.status_code == 200:
            return response.content.decode('utf-8')
        return None

    def run(self, pagebar, rnd):
        params_dict = {
            "ajwvr": 6,
            "domain": 100505,
            "refer_flag": "1087030701_2975_2023_0",
            "is_hot": 1,
            "pagebar": pagebar,
            "pl_name": "Pl_Official_MyProfileFeed__20",
            "id": 1005051558473534,
            "script_uri": "/u/1558473534",
            "feed_type": 0,
            "page": 1,
            "pre_page": 1,
            "domain_op": 100505,
            "__rnd": rnd,
        }
        start_url = "https://weibo.com/p/aj/v6/mblog/mbloglist"
        response = self.get_response(start_url, params_dict)
        print(response)

if __name__ == '__main__':
    wb_spider = WeiboSpider()

    dtime = datetime.datetime.now()
    un_time = time.mktime(dtime.timetuple())
    rnd = int(f'{int(un_time)}{rd.randint(100, 999)}')

其實(shí)params_dict中的部分參數(shù)可能也是不需要的,這里想去實(shí)驗(yàn)的可以去嘗試一下
現(xiàn)在請求已經(jīng)完成了,看下打印的結(jié)果,由于內(nèi)容較多,就貼個(gè)圖:

采集內(nèi)容

接下來就是對獲取到的內(nèi)容進(jìn)行分析,拿到我們想要的數(shù)據(jù)了,這里就隨便取幾個(gè)數(shù)據(jù)了:博主、博主頭像、時(shí)間、文字內(nèi)容、圖片內(nèi)容、評論數(shù)、點(diǎn)贊數(shù)。
之前的幾篇文中已經(jīng)實(shí)際應(yīng)用了一些解析的用法了,這里就不仔細(xì)寫了,大概寫一下思路吧:

首先我們要取的是一篇一篇的博客內(nèi)容,上面的內(nèi)容可能是一篇文字內(nèi)容對應(yīng)多個(gè)圖片,所以在解析的時(shí)候需要對應(yīng)起來,我們看下之前復(fù)制出來的Html塊:


加載的Html塊

通過左邊的 + - 符號可以很清晰的展現(xiàn)出每一篇博客的html塊,每一塊是由一個(gè)div組成,這樣我們可以先取div塊,然后再從每個(gè)div塊中再獲取我們所需要的數(shù)據(jù),可以這樣處理:
首先獲取每篇博客的div塊,也就是博客列表,列表中是每篇博客div塊的Element對象

        # 相應(yīng)信息中獲取加載的數(shù)據(jù)信息
        data_dict = json.loads(response)
        html_content = etree.HTML(data_dict['data'])  # 轉(zhuǎn)為Element對象
        # 獲取每篇博客的div塊
        blog_list = html_content.xpath('//div[@action-type="feed_list_item"]')

看下結(jié)果:

[<Element div at 0x39801c8>, <Element div at 0x3980b48>, <Element div at 0x3980bc8>, <Element div at 0x3980b88>, <Element div at 0x3980c48>, <Element div at 0x3980d08>, <Element div at 0x3980cc8>, <Element div at 0x3980d48>, <Element div at 0x3980d88>, <Element div at 0x3980c08>, <Element div at 0x3980dc8>, <Element div at 0x3980e08>, <Element div at 0x3980e48>, <Element div at 0x395a988>, <Element div at 0x395aa08>]

然后再遍歷解析每篇博客,獲取我們所需要的數(shù)據(jù):

        for blog in blog_list:
            blog_item = dict()
            # 博主頭像
            blog_item['blogger_photo'] = blog.xpath('descendant::img[@class="W_face_radius"]/@src')[0]
            # 博主昵稱:這個(gè)信息有很多地方都出現(xiàn)了,可以選擇一個(gè)較好取值的,我選的是跟微博內(nèi)容在一個(gè)地方的,用nick-name屬性表示
            blog_item['blogger_name'] = blog.xpath('descendant::div[contains(@class, "WB_text")]/@nick-name')[0]
            # 博客時(shí)間
            blog_item['blog_time'] = blog.xpath('descendant::a[@node-type="feed_list_item_date"]/@title')[0]
            # 博客文字內(nèi)容, 這里注意的是有個(gè) \u200b 字符,,這是個(gè)0長度的比較特殊的字符,編碼可能轉(zhuǎn)不過來,所以做個(gè)簡單替換處理
            blog_item['blog_content'] = blog.xpath('descendant::div[contains(@class, "WB_text")]/text()')[0].strip().replace('\u200b', '')
            # 博客圖片內(nèi)容
            blog_item['blog_picture_list'] = blog.xpath('descendant::ul[@node-type="fl_pic_list"]//li/img/@src')
            # 評論數(shù)
            blog_item['blog_comment'] = blog.xpath('descendant::span[@node-type="comment_btn_text"]//em[last()]/text()')[0]
            # 點(diǎn)贊數(shù), 在這里有個(gè)處理:當(dāng)沒有點(diǎn)贊的時(shí)候會(huì)顯示出一個(gè) “贊” 字, 所以當(dāng)是 “贊” 的時(shí)候點(diǎn)贊數(shù)是 0
            blog_likestar = blog.xpath('descendant::span[@node-type="like_status"]//em[last()]/text()')[0]
            blog_item['blog_likestar'] = '0' if blog_likestar == '贊' else blog_likestar
            yield blog_item

貼一下主要實(shí)現(xiàn)代碼:

    def get_response(self, req_url, params_dict=None):
        """
        請求
        :param req_url:
        :param params_dict:
        :return:
        """
        if params_dict:
            response = requests.get(req_url, params=params_dict, headers=self._headers)
        else:
            response = requests.get(req_url, headers=self._headers)
        if response.status_code == 200:
            return response.content.decode('utf-8')
        return None

    def run(self, pagebar, rnd):
        """
        主函數(shù)
        :param pagebar:
        :param rnd:
        :return:
        """
        params_dict = {
            "ajwvr": 6,
            "domain": 100505,
            "refer_flag": "1087030701_2975_2023_0",
            "is_hot": 1,
            "pagebar": pagebar,
            "pl_name": "Pl_Official_MyProfileFeed__20",
            "id": 1005051558473534,
            "script_uri": "/u/1558473534",
            "feed_type": 0,
            "page": 1,
            "pre_page": 1,
            "domain_op": 100505,
            "__rnd": rnd,
        }
        start_url = "https://weibo.com/p/aj/v6/mblog/mbloglist"
        # 1.發(fā)出請求,獲取響應(yīng)
        response = self.get_response(start_url, params_dict)

        # 2.數(shù)據(jù)解析
        blog_content = self.get_blog_list(response)

        # 3.輸出采集到的內(nèi)容, 想存儲(chǔ)的可自選存儲(chǔ)方式
        for blog in blog_content:
            print(blog)

    def get_blog_list(self, response):
        """
        獲取博客列表
        :param response:
        :return:
        """
        # 相應(yīng)信息中獲取加載的數(shù)據(jù)信息
        data_dict = json.loads(response)
        html_content = etree.HTML(data_dict['data'])  # 轉(zhuǎn)為Element對象
        # 獲取每篇博客的div塊
        blog_list = html_content.xpath('//div[@action-type="feed_list_item"]')

        # 遍歷解析每篇博客內(nèi)容
        blog_content = self.data_parse(blog_list)
        return blog_content

    def data_parse(self, blog_list):
        """
        解析每篇博客內(nèi)容
        :param response:
        :return:
        """
        for blog in blog_list:
            blog_item = dict()
            # 博主頭像
            blog_item['blogger_photo'] = blog.xpath('descendant::img[@class="W_face_radius"]/@src')[0]
            # 博主昵稱:這個(gè)信息有很多地方都出現(xiàn)了,可以選擇一個(gè)較好取值的,我選的是跟微博內(nèi)容在一個(gè)地方的,用nick-name屬性表示
            blog_item['blogger_name'] = blog.xpath('descendant::div[contains(@class, "WB_text")]/@nick-name')[0]
            # 博客時(shí)間
            blog_item['blog_time'] = blog.xpath('descendant::a[@node-type="feed_list_item_date"]/@title')[0]
            # 博客文字內(nèi)容, 這里注意的是有個(gè) \u200b 字符,,這是個(gè)0長度的比較特殊的字符,編碼可能轉(zhuǎn)不過來,所以做個(gè)簡單替換處理
            blog_item['blog_content'] = blog.xpath('descendant::div[contains(@class, "WB_text")]/text()')[0].strip().replace('\u200b', '')
            # 博客圖片內(nèi)容
            blog_item['blog_picture_list'] = blog.xpath('descendant::ul[@node-type="fl_pic_list"]//li/img/@src')
            # 評論數(shù)
            blog_item['blog_comment'] = blog.xpath('descendant::span[@node-type="comment_btn_text"]//em[last()]/text()')[0]
            # 點(diǎn)贊數(shù), 在這里有個(gè)處理:當(dāng)沒有點(diǎn)贊的時(shí)候會(huì)顯示出一個(gè) “贊” 字, 所以當(dāng)是 “贊” 的時(shí)候點(diǎn)贊數(shù)是 0
            blog_likestar = blog.xpath('descendant::span[@node-type="like_status"]//em[last()]/text()')[0]
            blog_item['blog_likestar'] = '0' if blog_likestar == '贊' else blog_likestar
            yield blog_item

再貼一下main:

if __name__ == '__main__':
    wb_spider = WeiboSpider()

    dtime = datetime.datetime.now()
    un_time = time.mktime(dtime.timetuple())
    rnd = int(f'{int(un_time)}{rd.randint(100, 999)}')

    for i in range(2):
        print(f'{"=" * 30}第 {i + 1} 次數(shù)據(jù)加載')
        wb_spider.run(i, rnd)
        time.sleep(10)

看一下最終打印結(jié)果,由于數(shù)據(jù)較多,這里貼個(gè)圖:


采集博客信息

4.結(jié)語

雖然看起來篇幅很長,其實(shí)也是挺簡單基礎(chǔ)的一個(gè)采集小實(shí)例,就是簡單說了下Ajax異步加載數(shù)據(jù)獲取的方式跟分析的簡單步驟,如果大家有更好的方法可以留言一起交流。下一篇再用一個(gè)完整的實(shí)例來加深對Ajax異步加載數(shù)據(jù)采集的印象。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前端開發(fā)面試題 面試題目: 根據(jù)你的等級和職位的變化,入門級到專家級,廣度和深度都會(huì)有所增加。 題目類型: 理論知...
    怡寶丶閱讀 2,682評論 0 7
  • Yahoo!的Exceptional Performance團(tuán)隊(duì)為改善Web性能帶來最佳實(shí)踐。他們?yōu)榇诉M(jìn)行了一系列...
    拉風(fēng)的老衲閱讀 1,956評論 0 1
  • 點(diǎn)我查看本文集的說明及目錄。 本項(xiàng)目相關(guān)內(nèi)容( github傳送 )包括: 實(shí)現(xiàn)過程: CH4 創(chuàng)建社交網(wǎng)站 CH...
    學(xué)以致用123閱讀 1,521評論 1 2
  • HTTP基本原理 URI、URL、URN(Uninform Resource) URI(Identifier):統(tǒng)...
    GHope閱讀 2,283評論 2 26
  • 老人回首來時(shí)路。有人悔不當(dāng)初,有人難得糊涂,有人感慨萬千。你我不過一捧黃土。 這告訴我們一個(gè)道理――努力奮斗是沒有...
    借我扁舟一葉閱讀 176評論 0 1

友情鏈接更多精彩內(nèi)容