爬取大眾點評網(wǎng)的某城市美食店鋪數(shù)據(jù)

前言

某旅游城市在今年的十一期間再次火爆了一把,城市的各種美食確實讓人垂涎欲滴。因此,個人萌生了爬取該城市美食店鋪信息的想法。

一、確定爬取的url

1.首先用瀏覽器打開大眾點評網(wǎng)站www.dianping.com,然后點擊城市鏈接 ,再點擊美食鏈接進(jìn)入城市美食頁面。

地址為:http://www.dianping.com/changsha/ch10/p0

2.改變頁碼,發(fā)現(xiàn)只有最后的px會發(fā)生變化。因此,只需要簡單構(gòu)建循環(huán)即可構(gòu)造url地址。

二、開始抓取


1.直接抓取

查看網(wǎng)頁源代碼,發(fā)現(xiàn)可以搜索到網(wǎng)頁中存在的信息,因此基本可以確定是靜態(tài)網(wǎng)頁。


僅僅添加useragent直接進(jìn)行抓取后,發(fā)現(xiàn)出現(xiàn)了302狀態(tài)碼進(jìn)行了跳轉(zhuǎn)。這種情況下,應(yīng)該是需要增加請求頭信息。

2.構(gòu)造請求頭

1.查看網(wǎng)頁請求頭如下圖!


幾次刷新頁面后發(fā)現(xiàn),改變的數(shù)據(jù)僅僅是cookie,其它請求頭信息均為固定信息。每次cookie均發(fā)生了改變,但是并沒有進(jìn)行兩次加載,說明cookie信息應(yīng)該是本地生成的。cookie信息中有兩項信息會發(fā)生改變,


其中,_lxsdk_s每次的改變很有規(guī)律,為最后兩位數(shù)加20或者21(具體是20還是21自己可以刷新幾次看一下)。但是另一項就比較復(fù)雜,看起來像是時間戳,但是嘗試后發(fā)現(xiàn)并不是。既然是本地生成的,那么查看js應(yīng)該可以看到生成方式,個人嘗試了一下,無奈js能力有限。

但是,既然是本地生成的,那么我們可以進(jìn)行分析,在瀏覽器和頁面內(nèi)容均沒有發(fā)生改變的情況下,改變的只有加載時間,我們可以大膽猜測這個數(shù)據(jù)還是和時間有關(guān)系。而且response headers中還有一項Set-Cookie,里面的數(shù)據(jù)僅有時間在發(fā)生改變,基本可以確認(rèn)Hm_lpvt的改變是由時間引起的。


```python

17-Nov-2022 07:22:07 GMT 1605596557

17-Nov-2022 07:31:29 GMT 1605597742

17-Nov-2022 07:31:59 GMT 1605598305

```

如圖,查看幾次請求信息,最后觀察到每一次的Hm_lpvt的改變量為前次請求的時間間隔,其實也就是每次請求的Hm_lpvt信息,實際上是與上一次請求時間以及第一次請求時間相關(guān)的。

那么我們可以就此構(gòu)造cookie??紤]到萬一某次請求出現(xiàn)問題方便再次構(gòu)造cookie,因此本人將構(gòu)造cookie需要的三項數(shù)據(jù)寫入txt文件,每次進(jìn)行讀取和寫入操作。

```python

`def get_cookie():

? ? new = """fspop=test; _lxsdk_cuid=175a63b9732c8-045b6a35f56618-230346d-1fa400-175a63b9733c8; _lxsdk=175a6

? ? 3b9732c8-045b6a35f56618-230346d-1fa400-175a63b9733c8; _hc.v=500ca2b8-99ea-2512-79b9-579fd5ee6aab.1604811726; s_View

? ? Type=10; _lx_utm=utm_source%3DBaidu%26utm_medium%3Dorganic; cy=344; cye=changsha; _dp.ac.v=136a299d-309b-401a-93d4-

? ? 15565e9b3ec9; ua=dpuser_8849637005; ctu=6785bc81e314ca567cbe3eeabeca236ead9ce92b79a054a5ce52c4d63f36ce8d; Hm_lvt_602b80cf8079ae6591966cc70a3940e7=1604811726,1604835093,1604897708; _lxsdk_s=175ab5b8dc6-68-5d-822%7C%7C{}; Hm_lpvt_602b80cf8079ae6591966cc70a3940e7={}

? ? """

? ? with open("cookie.txt", 'r') as f:

? ? ? ? for line in f.readlines():

? ? ? ? ? ? time_cookie = json.loads(line)

? #將時間轉(zhuǎn)換成字符串

? ? timearray1 = time.strptime(time_cookie[1]['time'], "%Y-%m-%d %H:%M:%S")

? ? timearray0 = time.strptime(time_cookie[0]['time'], "%Y-%m-%d %H:%M:%S")

? #求前兩次的時間差

? ? timedelta = int(time.mktime(timearray1)) - int(time.mktime(timearray0))

? #構(gòu)造新的cookie需要的數(shù)據(jù)

? ? new_cookie = time_cookie[1]['cookie'] + timedelta

? ? timearray = time.localtime(int(time.time()))

? ? new_time = time.strftime("%Y-%m-%d %H:%M:%S", timearray)

? ? new_num = time_cookie[1]['num'] + 20

? #將新的cookie存入文件,并刪除第一項cookie

? ? time_cookie.append({'time':new_time, 'cookie':new_cookie,'num' : new_num})

? ? time_cookie.pop(0)

? ? cookie_json = json.dumps(time_cookie)

? ? with open("cookie.txt", 'w', ) as f:

? ? ? ? f.write(cookie_json)

? ? return new.format(str(new_num), str(new_cookie))

cookie =get_cookie()

```

構(gòu)造cookie后再次進(jìn)行請求,果然,請求成功。

3.抓取信息

為了方便分析頁面內(nèi)容,因此將網(wǎng)頁保存在了本地。在此僅進(jìn)行了店鋪名稱,店鋪得分,店鋪評論數(shù)和人均消費信息的抓取。使用了pyquery和re模塊進(jìn)行網(wǎng)頁解析。

```python

with open('dianping.html', 'r', encoding='utf-8') as f:

? ? b = ''

? ? for line in f.readlines():

? ? ? ? b += line

doc = pq(b)

shop_items = doc('#shop-all-list')

shoplist =shop_items('li').items()

shops = []

for shop in shoplist:

? ? shop_dict = {}

? ? shop_name = re.search("<h4>(.*?)</h4>", str(shop), re.S).group(1)

? ? shop_star = re.search(r'<div class="star_score.*?">(.*?)</div>', str(shop), re.S).group(1)

? ? comments = re.search(r'"shop_iwant_review_click.*?<b>(.*?)</b>', str(shop), re.S).group(1)

? ? consume = re.search(r'class="mean-price".*?<b>(.*?)</b>', str(shop), re.S).group(1)

? ? shop_dict['shop_name'] = shop_name

? ? shop_dict['star'] = shop_star

? ? shop_dict['comments'] = comments

? ? shop_dict['consume'] = consume

? ? shops.append(shop_dict)

pprint.pprint(shops)

```

抓取的結(jié)果如圖所示:


發(fā)現(xiàn)評論數(shù)和人均消費的數(shù)字,除了‘1’以外均無法正常顯示。我們再次查看網(wǎng)頁元素。


4.信息解密

如上圖所示,網(wǎng)頁部分內(nèi)容進(jìn)行了加密,無法正常爬取。可以看到右上角位置有css的指示,打開此鏈接,如下圖


其中有很多個字體文件,但是每個字體文件連接后面均說明了文件內(nèi)容,我們需要的就是shopNum(店鋪數(shù)字)字體文件內(nèi)容。

點擊此鏈接進(jìn)行下載,并使用fontTools進(jìn)行轉(zhuǎn)換后查看。

```python

from fontTools.ttLib import TTFont

font = TTFont(r'C:\Users\zhaohengcai\Downloads\fb3304fa.woff')

font.saveXML('local_font.xml')

```

查看內(nèi)容如圖


對照店鋪源代碼與網(wǎng)頁顯示內(nèi)容,最后可以發(fā)現(xiàn)與上圖的藍(lán)色框內(nèi)的數(shù)據(jù)對應(yīng)。但是3-11對應(yīng)的網(wǎng)頁顯示內(nèi)容為3-9和0,我們需要自己進(jìn)行轉(zhuǎn)換。

```python

passage = """

? ? <GlyphID id="3" name="unie486"/>

? ? <GlyphID id="4" name="unieb23"/>

? ? <GlyphID id="5" name="unif298"/>

? ? <GlyphID id="6" name="unif240"/>

? ? <GlyphID id="7" name="unif001"/>

? ? <GlyphID id="8" name="unie997"/>

? ? <GlyphID id="9" name="unif59a"/>

? ? <GlyphID id="10" name="uniee87"/>

? ? <GlyphID id="11" name="unif876"/>

? ? """

keylist = re.findall('<GlyphID id="(.*?)" name="uni(.*?)"/>', passage)

num_dict = {}

for item in keylist:

? ? if int(item[0]) != 11:

? ? ? ? num_dict[r'\u' + item[1]] = int(item[0]) - 1

? ? else:

? ? ? ? num_dict[r'\u' + item[1]] = 0

print(num_dict)

返回去將解析網(wǎng)頁內(nèi)容的代碼進(jìn)行適當(dāng)修改,使用re進(jìn)行替換,

num_key = {'\ue486': '2', '\ueb23': '3', '\uf298': '4', '\uf240': '5', '\uf001': '6', '\ue997': '7', '\uf59a': '8', '\uee87': '9', '\uf876': '0'}

with open('dianping.html', 'r', encoding='utf-8') as f:

? ? b = ''

? ? for line in f.readlines():

? ? ? ? b += line

doc = pq(b)

shop_items = doc('#shop-all-list')

shoplist =shop_items('li').items()

shops = []

for shop in shoplist:

? ? shop_dict = {}

? ? shop_name = re.search("<h4>(.*?)</h4>", str(shop), re.S).group(1)

? ? shop_star = re.search(r'<div class="star_score.*?">(.*?)</div>', str(shop), re.S).group(1)

? ? comments = re.search(r'"shop_iwant_review_click.*?<b>(.*?)</b>', str(shop), re.S).group(1)

? ? #將<svgmtsi class="shopNum">(.*?)</svgmtsi>的內(nèi)容進(jìn)行替換

? ? comments_key = re.findall('<svgmtsi class="shopNum">(.*?)</svgmtsi>', str(comments), re.S)

? ? for item in comments_key:

? ? ? ? pattern? = re.compile('<svgmtsi class="shopNum">{}</svgmtsi>'.format(item))

? ? ? ? comments = re.sub(pattern, num_key[item], comments)

? ? consume = re.search(r'class="mean-price".*?<b>(.*?)</b>', str(shop), re.S).group(1)

? ? #將<svgmtsi class="shopNum">(.*?)</svgmtsi>的內(nèi)容進(jìn)行替換

? ? consume_key = re.findall('<svgmtsi class="shopNum">(.*?)</svgmtsi>', str(consume), re.S)

? ? for item in consume_key:

? ? ? ? pattern = re.compile('<svgmtsi class="shopNum">{}</svgmtsi>'.format(item))

? ? ? ? consume= re.sub(pattern, num_key[item], consume)

? ? shop_dict['shop_name'] = shop_name

? ? shop_dict['star'] = shop_star

? ? shop_dict['comments'] = comments

? ? shop_dict['consume'] = consume

? ? shops.append(shop_dict)

pprint.pprint(shops)

```

運行結(jié)果如下:


三、將數(shù)據(jù)保存到數(shù)據(jù)庫

這樣我們的工作基本上就完成了,然后我們可以將數(shù)據(jù)存入數(shù)據(jù)庫:

```python

myclient = pymongo.MongoClient('localhost', 27017)

dianping_db = myclient['dianping']

dianping_col = dianping_db['dianping_food']

dianping_col.insert_one(shop_dict)

```

之后,我們將代碼進(jìn)行整合。

```python

import json

import pprint

import random

import re

import time

import pymongo

import requests

from pyquery import PyQuery as pq

user_agents = [

? ? 'Mozilla/5.0(Macintosh;Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36',

? ? 'Mozilla/5.0(Windows;U;MSIE 9.0;Windows NT 9.0;en-US)',

? ? 'Mozilla/5.0(Windows NT 6.1) AppleWebKit/537.2 (KHTML, like Gecko) Chrome/22.0.1216.0Safari/537.2',

? ? 'Mozilla/5.0(X11; Ubuntu;Linux i686;rv:15.0) Gecko/20100101 Firefox/15.0.1']

headers = {

? ? 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',

? ? 'User-Agent': random.choice(user_agents),

? ? 'Connection': 'keep - alive',

? ? 'Host': 'www.dianping.com',

? ? 'Upgrade-Insecure-Requests': '1',

}

myclient = pymongo.MongoClient('localhost', 27017)

dianping_db = myclient['dianping']

dianping_col = dianping_db['dianping_food']

time_cookie = []

time_cookie.append({"time": "2020-11-17 08:07:36", "cookie": 1605571660, "num": 40})

time_cookie.append({"time": "2020-11-17 08:09:37", "cookie": 1605571671, "num": 60})

timestamp = int(time.time())

timearray = time.localtime(timestamp)

def get_cookie():

? ? new = """_lxsdk_cuid=175a63b9732c8-045b6a35f56618-230346d-1fa400-175a63b9733c8; _lxsdk=175a63b9732c8-045b6a35f56618

? ? -230346d-1fa400-175a63b9733c8; _hc.v=500ca2b8-99ea-2512-79b9-579fd5ee6aab.1604811726; s_ViewType=10; cy=344; cye=ch

? ? angsha; _dp.ac.v=136a299d-309b-401a-93d4-15565e9b3ec9; ua=dpuser_8849637005; ctu=6785bc81e314ca567cbe3eeabeca236ead

? ? 9ce92b79a054a5ce52c4d63f36ce8d; fspop=test; Hm_lvt_602b80cf8079ae6591966cc70a3940e7=1604986265,1605266096,16055297

? ? 94,1605571653; _lxsdk_s=175d38722cb-a29-e6e-f2b%7C%7C{}; Hm_lpvt_602b80cf8079ae6591966cc70a3940e7={}

? ? """

? ? with open("cookie.txt", 'r') as f:

? ? ? ? for line in f.readlines():

? ? ? ? ? ? time_cookie = json.loads(line)

? ? timearray1 = time.strptime(time_cookie[1]['time'], "%Y-%m-%d %H:%M:%S")

? ? timearray0 = time.strptime(time_cookie[0]['time'], "%Y-%m-%d %H:%M:%S")

? ? timedelta = int(time.mktime(timearray1)) - int(time.mktime(timearray0))

? ? new_cookie = time_cookie[1]['cookie'] + timedelta

? ? timearray = time.localtime(int(time.time()))

? ? new_time = time.strftime("%Y-%m-%d %H:%M:%S", timearray)

? ? new_num = time_cookie[1]['num'] + 20

? ? time_cookie.append({'time': new_time, 'cookie': new_cookie, 'num': new_num})

? ? time_cookie.pop(0)

? ? cookie_json = json.dumps(time_cookie)

? ? with open("cookie.txt", 'w', ) as f:

? ? ? ? f.write(cookie_json)

? ? return new.format(str(new_num), str(new_cookie))

def get_page(url):

? ? a = get_cookie()

? ? new_cookie = {'Cookie': a}

? ? response = requests.get(url=url, headers=headers, cookies=new_cookie)

? ? with open('dianping.html', 'w', encoding='utf-8') as f:

? ? ? ? f.write(response.text)

? ? return response

def parse(response):

? ? doc = pq(response.text)

? ? shop_items = doc('#shop-all-list')

? ? shoplist =shop_items('li').items()

? ? shops = []

? ? num_key = {'\ue486': '2', '\ueb23': '3', '\uf298': '4', '\uf240': '5', '\uf001': '6', '\ue997': '7', '\uf59a': '8', '\uee87': '9', '\uf876': '0'}

? ? for shop in shoplist:

? ? ? ? shop_dict = {}

? ? ? ? shop_name = re.search("<h4>(.*?)</h4>", str(shop), re.S).group(1)

? ? ? ? shop_star = re.search(r'<div class="star_score.*?">(.*?)</div>', str(shop), re.S).group(1)

? ? ? ? comments = re.search(r'"shop_iwant_review_click.*?<b>(.*?)</b>', str(shop), re.S).group(1)

? ? ? ? comments_key = re.findall('<svgmtsi class="shopNum">(.*?)</svgmtsi>', str(comments), re.S)

? ? ? ? for item in comments_key:

? ? ? ? ? ? pattern = re.compile('<svgmtsi class="shopNum">{}</svgmtsi>'.format(item))

? ? ? ? ? ? comments = re.sub(pattern, num_key[item], comments)

? ? ? ? consume = re.search(r'class="mean-price".*?<b>(.*?)</b>', str(shop), re.S).group(1)

? ? ? ? consume_key = re.findall('<svgmtsi class="shopNum">(.*?)</svgmtsi>', str(consume), re.S)

? ? ? ? for item in consume_key:

? ? ? ? ? ? pattern = re.compile('<svgmtsi class="shopNum">{}</svgmtsi>'.format(item))

? ? ? ? ? ? consume = re.sub(pattern, num_key[item], consume)

? ? ? ? shop_dict['shop_name'] = shop_name

? ? ? ? shop_dict['star'] = shop_star

? ? ? ? shop_dict['comments'] = comments

? ? ? ? shop_dict['consume'] = consume

? ? ? ? dianping_col.insert_one(shop_dict)

? ? ? ? shops.append(shop_dict)

? ? pprint.pprint(shops)

def main():

? ? for page_num in range(10):

? ? ? ? print('-------------正在爬取第{}頁數(shù)據(jù)--------------'.format(page_num))

? ? ? ? url = 'http://www.dianping.com/changsha/ch10/p{}'.format(page_num)

? ? ? ? response = get_page(url)

? ? ? ? parse(response)

? ? ? ? time.sleep(3)

if __name__ == '__main__':

? ? main()

```

四、總結(jié)

一、本次爬取的難點主要有兩個方面:

1.cookie的構(gòu)造;

2.店鋪信息的解密。

cookie的構(gòu)造,對擅長js解密的人應(yīng)該比較簡單,不擅長js的話,只能像我一樣進(jìn)行分析。

店鋪信息的解密,有很多人都寫了,在此不再贅述。

二、關(guān)于后續(xù)爬取的思考

其實應(yīng)該講爬取的內(nèi)容存入mysql數(shù)據(jù)庫,方便后續(xù)爬取評論內(nèi)容的時候進(jìn)行關(guān)聯(lián)。使用mongoDB不方便數(shù)據(jù)的關(guān)聯(lián)。

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

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

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