Python爬取京東手機數據

來~我這有個需求你做一哈

爬取京東上所有華爲手機的折扣價(有則獲取,無則不用)、價格手機品類評論數量評論標籤

注意點

  • 這裏對於價格的區分是這樣的,對於有明顯標識“折扣價”的取其數值作爲折扣價,此時價格就是其原價;而對於沒有明顯標識的只有一個價格的情況,在後面我們進行數據提取時再提

  • 手機品類就是這個商品的名稱罷,同時包括手機所有的規格配置,如魅惑紅4G+32G、銀灰色 8G+128G等

  • 評論數評論標籤則是在這裏

敲鍵盤之前

手機ID、手機名稱獲取

老樣子,拿到一個需求後我們要分析需求對應的數據都要在哪裏獲取
首先我們看一下這個列表頁中的手機信息都是哪冒出來的

找了找網頁源碼,發現只有30條記錄,But 這一頁我掰着手指數了數有60個手機,另外30個被吃了?

不可能的,那麼看看是不是動態加載的吧


果不其然,我們在 s_new.php....這個接口中找到了剩下動態加載的 30 條手機數據,而實際上每個頁面的 60 條數據都是通過這個類似的接口加載的,每批 30 個,分兩批;這兩批的接口 url 還有點差異

這兩次我們可以看作是單數頁雙數頁,明顯雙數頁單數頁多了好一串數字,實際上這些數字經過比較可以發現就是那單數頁手機的 ID ,而且在實際調用時只要改變頁數及偏移量,保持這樣的接口,而不用改動這些 ID

self.d_page_url = 'https://search.jd.com/s_new.php?keyword=%E5%8D%8E%E4%B8%BA&enc=utf-8&' \
                          'qrst=1&rt=1&stop=1&vt=2&bs=1&wq=%E5%8D%8E%E4%B8%BA&cid2=653&cid3=655&' \
                          'ev=exbrand_%E5%8D%8E%E4%B8%BA%EF%BC%88HUAWEI%EF%BC%89%5E&page={}&s={}&click=0'

self.s_page_url = 'https://search.jd.com/s_new.php?keyword=%E5%8D%8E%E4%B8%BA&enc=utf-8&qrst=1&rt=1&' \
                          'stop=1&vt=2&bs=1&wq=%E5%8D%8E%E4%B8%BA&cid2=653&cid3=655&ev=exbrand_%E5%8D%8E%E4%B8' \
                          '%BA%EF%BC%88HUAWEI%EF%BC%89%5E&page={}&s={}&scrolling=y&log_id=1546069310.93962&tpl=3_M&' \
                          'show_items=7694047,5821455,7421462,8735304,100000822981,6946605,7081550,100000650837,' \
                          '7479912,100000766433,100000820311,100002293114,8240587,100001467225,6733024,6703015,' \
                          '34593872687,100000972490,6840907,5963066,6001239,28245104630,8058010,5826236,100000767811,' \
                          '8485229,7123633,100000084109,6737464,6055054'

我們可以從這個接口中獲取每個手機的 ID (來構成每個手機的 url ),手機的名稱

手機折扣價、價格獲取

這裏我們就來講一講手機價格,手機的價格呢在點擊進入一個手機後,我們需要在另一個接口中獲取:

https://c0.3.cn/stock?skuId=7694047&cat=9987,653,655&venderId=1000000904&area=1_72_2799_0&buyNum=1&choseSuitSkuIds=&extraParam={%22originid%22:%221%22}&ch=1&fqsp=0&pduid=14925274412821890795342&pdpin=jd_707f2cd751b1b&callback=jQuery7641519

首先這個 URL 本身帶有花括號 { } 這也就是說我們在用 format 格式化的時候會遇到問題,怎麼辦呢,這裏我們要轉義一下這個花括號,{{ }} 通過兩個括號的形式來轉義

self.phone_price = 'https://c0.3.cn/stock?skuId={}&cat=9987,653,655&venderId=1000004259&area=1_72_2799_0&' \
                           'buyNum=1&choseSuitSkuIds=&extraParam={{%22originid%22:%221%22}}&ch=1&fqsp=0&' \
                           'pduid=1566822370&pdpin=jd_707f2cd751b1b&callback=jQuery7969932'

再來看看這裏的 jdPrice ,m 對應的值應該是一個最大值,op 對應之前的價格,p 對應現在的價格;當然這裏是兩者相等,也就是說這款手機沒有過降價,所以我們的折扣價就沒有,價格就給定這裏的 p ;那若是 p < op 的情況就很明顯,給定折扣價爲 p ,價格爲 op

手機評論數、評論標籤獲取

來看看剩下的評論數評論標籤,一樣我們通過找加載的接口可以發現

https://sclub.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98vv8623&productId=8758880&score=0&[圖片上傳中...(2019-01-11_225325.png-e1e00f-1547220671822-0)]
sortType=5&page=0&pageSize=10&isShadowSku=0&fold=1

這個接口中也有評論數,但是最後分析可能是有很多的爬蟲在爬取京東的商品評論來做用戶分析,然後這個接口就做了IP的反爬處理,同一個IP訪問的次數有限,一定次數後將無法返回數據,你可以試試現在是否還是這樣...總之比較蛋疼,就沒有接着獲取評論的標籤

這裏需求就改爲只獲取評論數(嗯我的地盤我說了算,當然也可以用過代理IP池的方法獲取評論標籤

但是光是華爲的手機我最後爬下來就有3萬+的數據,所以這裏我又找到一個接口來獲取評論數

https://club.jd.com/comment/productCommentSummaries.action?referenceIds=8758880&callback=jQuery8105871&_=1547217760388

手機的不同規格獲取

接下來還有個問題就是手機的不同規格應該如何獲取了,這時候我是通過找到網頁源碼中的這裏

這裏的 skuId 就是各種規格的手機的 ID 了,所以獲取也就不在話下(關於這一點,應該有對應的接口,大家可以找找看呀)

開始敲鍵盤

初始化的一些鏈接

        # 搜索第一頁的鏈接 用於獲取總頁數
        self.search_url = 'https://search.jd.com/search?keyword=%E5%8D%8E%E4%B8%BA&enc=utf-8&qrst=1&rt=1&' \
                          'stop=1&vt=2&bs=1&wq=%E5%8D%8E%E4%B8%BA&cid2=653&' \
                          'cid3=655&ev=exbrand_%E5%8D%8E%E4%B8%BA%EF%BC%88HUAWEI%EF%BC%89%5E&page=1&s=1&click=0'
        # 單頁
        self.d_page_url = 'https://search.jd.com/s_new.php?keyword=%E5%8D%8E%E4%B8%BA&enc=utf-8&' \
                          'qrst=1&rt=1&stop=1&vt=2&bs=1&wq=%E5%8D%8E%E4%B8%BA&cid2=653&cid3=655&' \
                          'ev=exbrand_%E5%8D%8E%E4%B8%BA%EF%BC%88HUAWEI%EF%BC%89%5E&page={}&s={}&click=0'
        # 雙頁
        self.s_page_url = 'https://search.jd.com/s_new.php?keyword=%E5%8D%8E%E4%B8%BA&enc=utf-8&qrst=1&rt=1&' \
                          'stop=1&vt=2&bs=1&wq=%E5%8D%8E%E4%B8%BA&cid2=653&cid3=655&ev=exbrand_%E5%8D%8E%E4%B8' \
                          '%BA%EF%BC%88HUAWEI%EF%BC%89%5E&page={}&s={}&scrolling=y&log_id=1546069310.93962&tpl=3_M&' \
                          'show_items=7694047,5821455,7421462,8735304,100000822981,6946605,7081550,100000650837,' \
                          '7479912,100000766433,100000820311,100002293114,8240587,100001467225,6733024,6703015,' \
                          '34593872687,100000972490,6840907,5963066,6001239,28245104630,8058010,5826236,100000767811,' \
                          '8485229,7123633,100000084109,6737464,6055054'
        # 來源頁,需要在訪問單雙頁的接口時帶上
        self.referer = 'https://search.jd.com/search?keyword=%E5%8D%8E%E4%B8%BA&enc=utf-8&' \
                       'qrst=1&rt=1&stop=1&vt=2&bs=1&wq=%E5%8D%8E%E4%B8%BA&cid2=653&cid3=655&' \
                       'ev=exbrand_%E5%8D%8E%E4%B8%BA%EF%BC%88HUAWEI%EF%BC%89%5E&page={}&s={}&click=0'
        # 評論數接口
        self.good_url = 'https://club.jd.com/comment/productCommentSummaries.action?referenceIds={}' \
                        '&callback=jQuery5661745&_=1540019538438'
        # 評論標籤
        self.comments_type = 'https://sclub.jd.com/comment/productPageComments.action?' \
                             'callback=fetchJSON_comment98vv28643&productId={}&score=0&sortType=5' \
                             '&page=0&pageSize=10&isShadowSku=0&fold=1'
        self.user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' \
                          'Chrome/70.0.3538.102 Safari/537.36'
        # 價格
        self.phone_price = 'https://c0.3.cn/stock?skuId={}&cat=9987,653,655&venderId=1000004259&area=1_72_2799_0&' \
                           'buyNum=1&choseSuitSkuIds=&extraParam={{%22originid%22:%221%22}}&ch=1&fqsp=0&' \
                           'pduid=1566822370&pdpin=jd_707f2cd751b1b&callback=jQuery7969932'

請求第一頁得到總頁數,之後按照單雙頁的不同url,進行格式化,注意訪問時需要帶上,referer 以及 ua

    def gethtml2(self):
        s = requests.session()
        # 需要帶上ua
        r_page = s.get(self.search_url, headers={'user-agent': self.user_agent})
        page = re.findall(r'<span class="fp-text">.*?<b>1</b><em>/</em><i>(\d+)</i>.*?</span>', r_page.text, re.S)
        pages = int(page[0])*2 + 1
        print(u'共' + str(pages) + u'頁')
        for page in range(115, pages):
            # 單數頁
            if page % 2 == 1:
                referers = self.referer.format(page, 30*(int(page) - 1) + 1)
                header = {
                    'referer': referers,
                    'user-agent': self.user_agent,
                }
                r = s.get(self.d_page_url.format(page, 30 * (int(page) - 1) + 1), headers=header)
                self.gethtml1(s, page, r)
            if page % 2 == 0:
                referers = self.referer.format(page - 1, 30 * (int(page) - 2) + 1)
                header = {
                    'referer': referers,
                    'user-agent': self.user_agent,
                }
                r = s.get(self.s_page_url.format(page, 30 * (int(page) - 1) + 1), headers=header)
                self.gethtml1(s, page, r)

提取數據

    def gethtml1(self, s, page, r):
        # 一頁30個手機
        info_list = re.findall(r'<li class="gl-item" data-sku="(.*?)".*?>.*?<div class="p-price">(.*?)</div>'
                               r'.*?<em>(.*?)</em>.*?<a id="J_comment_.*?>.*?</a>.*?</li>', r.text, re.S)
        for infos in info_list:
            # 每一個手機
            info_id = infos[0]
            info_url = 'https://item.jd.com/{}.html'.format(infos[0])
            print(info_url)
            name = self.name_tool(infos[2])
            r_refprice = s.get(self.phone_price.format(info_id))
            ref_prices = re.findall(r'"jdPrice":{.*?"op":"(.*?)".*?"p":"(.*?)".*?}', r_refprice.text, re.S)
            price, ref_price = '', ''
            if ref_prices:
                ref_price = ref_prices[0][0]
                price = ref_prices[0][1]
                if ref_prices[0][0] == ref_prices[0][1]:
                    price = ''
                    ref_price = ref_prices[0][1]
            r1 = s.get(info_url)
            comment_num, comment_types = self.getcomment(s, infos[0])
            self.insertmysql(info_id, info_url, price, ref_price, name, comment_num, comment_types)
            # 每個手機的多種搭配方式
            colorsize = re.findall(r'colorSize: \[(.*?)\]', r1.text, re.S)

            if colorsize:
                skuIds = re.findall(r'"skuId":(\d+),', colorsize[0], re.S)
                for skuId in skuIds:
                    print(skuId)
                    skuid_url = 'https://item.jd.com/{}.html'.format(skuId)
                    r4 = s.get(skuid_url)
                    # 每種類型的標題
                    skuid_name = re.findall(r'<div class="sku-name">(.*?)</div>', r4.text, re.S)
                    if not skuid_name:
                        continue
                    skuid_name = self.name_tool(str(skuid_name[0])).strip()
                    r5 = s.get(self.phone_price.format(skuId))
                    # 每種類型的價格
                    skuid_prices = re.findall(r'"jdPrice":{.*?"op":"(.*?)".*?"p":"(.*?)".*?}', r5.text, re.S)
                    skuid_price, ref_skuid_price = '', ''
                    if skuid_prices:
                        ref_skuid_price = skuid_prices[0][0]
                        skuid_price = skuid_prices[0][1]
                        if skuid_prices[0][0] == skuid_prices[0][1]:
                            skuid_price = ''
                            ref_skuid_price = skuid_prices[0][1]
                    skuid_comment_num, skuid_comment_types = self.getcomment(s, skuId)
                    self.insertmysql(skuId, skuid_url, skuid_price, ref_skuid_price, skuid_name, skuid_comment_num, skuid_comment_types)

以及這裏處理評論數名稱的工具方法

    @staticmethod
    def price_tool(prices):
        price = re.findall(r'<i>(.*?)</i>', prices, re.S)[0]
        if 'data-price' in prices:
            price = re.findall(r'data-price="(.*?)">', prices, re.S)[0]
        return price

    @staticmethod
    def name_tool(name):
        name = re.sub(r'<.*?>', '', name)
        return name

    def getcomment(self, s, ids):
        # 好評數
        r2 = s.get(self.good_url.format(ids))
        # 好評類型
        # r3 = s.get(self.comments_type.format(ids), proxies=self.proxies)
        comment_num = re.findall(r'"CommentCount":(\d+),', r2.text, re.S)
        if comment_num:
            comment_num = comment_num[0]
        # comment_type = re.findall(r'\"hotCommentTagStatistics\":\[(.*?)\]', r3.text, re.S)
        comment_types = ''
        # if comment_type and len(comment_type):
        #     comment_types_list2 = re.findall(r'{.*?"name":"(.*?)".*?"count":(\d+),.*?}', comment_type[0], re.S)
        #     for c in comment_types_list2:
        #         comment_types += (c[0] + "(" + str(c[1]) + ") ")
        return comment_num, comment_types

結果.avi


print('微信公衆號搜索 "猿獅的單身日常" ,Java技術升級、蟲師修煉,我們 不見不散!')
print('也可以掃下方二維碼哦~')
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章