前言
某旅游城市在今年的十一期間再次火爆了一把疑务,城市的各種美食確實讓人垂涎欲滴暗甥。因此宝与,個人萌生了爬取該城市美食店鋪信息的想法焚廊。
一、確定爬取的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)讶请。