轉載於https://cuiqingcai.com/6341.html,對崔大大表示非常感謝
1.前言
在工作生活中,發現越來越多的人對大眾點評的數據感興趣,而大眾點評的反爬又是比較嚴格的。采取的策略差不多是寧可錯殺一萬,也不放過一個。有的時候正常瀏覽都會跳出驗證碼。
另外,在PC端的展示數據是通過CSS來控制的,從網頁上看不出來太大的區別,但是用普通的腳本取獲取時,會發現數據是獲取不到的,具體的源代碼是下面這樣的:

然,在搜資料的時候,你會發現,很多教程都是用的selenium之類的方法,效率太低,沒有啥技術含量。
所以,這篇文章的面向的對象就是PC端的大眾點評;目標是解決這種反爬蟲措施,使用requests獲取到干凈正確的數據;
跟着我,絕不會讓你失望。

2.正文開始
相信搞過大眾點評網站的同學都應該知道上面的這種是一個css反爬的方法,具體的解析操作,即將開始。
找到藏着秘密的css
當我們的鼠標在上面框框內的span上面點擊時,會發現右邊部分會相應的發生變化:

這張圖片很重要,很重要,很重要,我們要的值,幾乎都從這里匹配出來。
這里我們看到了“vxt20”這個變量對應的兩個像素值,前面的是控制用哪個數字,后面的是控制用哪一段的數字集合,先記下,后面要用,同時這里的值應該是6;
這里其實就是整個破解流程最關鍵的一步了。在這里我們看到了一個鏈接。
瞎貓當死耗子吧,點進去看看。
你會發現,返回的是一些數字,我一開始是不知道這是啥玩意的,看過一些大神的解析才知道,其實這里就是我們看到的數字的來源,也就是整個破解的源頭,知道了這些數字的含義,也就可以直接破解了整個反爬的流程。
現在直接看源代碼:

可以看到這里面的幾個關鍵數字:font-size:字體大小;還有幾個y的值,我到后面才知道原來這個y是個閾值,起的是個控制的作用。
所以,這一反爬的原理就是:
獲取屬性值與偏移量和閾值映射,然后從svg文件中找到真數據。
現在我們就要用到上面的像素值了。
1.把所有的值取絕對值;
2.用后面的值來選擇用哪個段的數字,這里的值是103,所以使用第三個段的數字集合;
3.因為每個字體是12個像素,所以用163/12=13.58,約等於14,那么我們數一下第14個數字是啥,沒錯,是6,和預期一樣。你可以多試驗幾次。
以上,就是整個破解的邏輯過程。
畫個流程圖,裝個逼:

3.Show Code
下面開始曬代碼,俗話說得好,天下代碼一大抄。
這里對主要的步驟代碼進行解釋, 如果你想獲取更多的代碼,請關注我的公眾號,並發送 “大眾點評”即可。。
1.獲取css_url及span對應的TAG值;
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
def get_tag(_list, offset=1):
# 從第一個開始查
_new_list = [data[0:offset] for data in _list]
if len(set(_new_list)) == 1:
# 如果set后只有一個值,說明全部重復,這個時候就把offset加1
offset += 1
return get_tag(_list, offset)
else:
_return_data = [data[0:offset - 1] for data in _list][0]
return _return_data
def get_css_and_tag(content):
"""
:param url: 待爬鏈接
:return: css鏈接,該span對應的tag
"""
find_css_url = re.search(r'href="([^"]+svgtextcss[^"]+)"', content, re.M)
if not find_css_url:
raise Exception("cannot find css_url ,check")
css_url = find_css_url.group(1)
css_url = "https:" + css_url
# 這個網頁上不同的字段是由不同的css段來進行控制的,所以要找到這個評論數據對應的tag,在這里返回的值為vx;而在獲取評論數據時,tag就是fu-;
# 具體可以觀察上面截圖的3個span對應的屬性值,相等的最長部分為vx
class_tag = re.findall("<b class=\"(.*?)\"></b>", content)
_tag = get_tag(class_tag)
return css_url, _tag
|
2.獲取屬性與像素值的對應關系
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
def get_css_and_px_dict(css_url):
con = requests.get(css_url, headers=headers).content.decode("utf-8")
find_datas = re.findall(r'(\.[a-zA-Z0-9-]+)\{background:(\-\d+\.\d+)px (\-\d+\.\d+)px', con)
css_name_and_px = {}
for data in find_datas:
# 屬性對應的值
span_class_attr_name= data[0][1:]
# 偏移量
offset = data[1]
# 閾值
position = data[2]
css_name_and_px[span_class_attr_name] = [offset, position]
return css_name_and_px
|
3.獲取svg文件的url
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
def get_svg_threshold_and_int_dict(css_url, _tag):
con = requests.get(css_url, headers=headers).content.decode("utf-8")
index_and_word_dict = {}
# 根據tag值匹配到相應的svg的網址
find_svg_url = re.search(r'span\[class\^="%s"\].*?background\-image: url\((.*?)\);' % _tag, con)
if not find_svg_url:
raise Exception("cannot find svg file, check")
svg_url = find_svg_url.group(1)
svg_url = "https:" + svg_url
svg_content = requests.get(svg_url, headers=headers).content
root = H.document_fromstring(svg_content)
datas = root.xpath("//text")
# 把閾值和對應的數字集合放入一個字典中
last = 0
for index, data in enumerate(datas):
y = int(data.xpath('@y')[0])
int_data = data.xpath('text()')[0]
index_and_word_dict[int_data] = range(last, y+1)
last = y
return index_and_word_dict
|
4. 獲取最終的值
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
def get_data(url ):
"""
:param page_url: 待獲取url
:return:
"""
con = requests.get(url, headers=headers).content.decode("utf-8")
# 獲取css url,及tag
css_url, _tag = get_css(con)
# 獲取css對應名與像素的映射
css_and_px_dict = get_css_and_px_dict(css_url)
# 獲取svg的閾值與數字集合的映射
svg_threshold_and_int_dict = get_svg_threshold_and_int_dict(css_url, _tag)
doc = etree.HTML(con)
shops = doc.xpath('//div[@id="shop-all-list"]/ul/li')
for shop in shops:
# 店名
name = shop.xpath('.//div[@class="tit"]/a')[0].attrib["title"]
print name
comment_num = 0
comment_and_price_datas = shop.xpath('.//div[@class="comment"]')
for comment_and_price_data in comment_and_price_datas:
_comment_data = comment_and_price_data.xpath('a[@class="review-num"]/b/node()')
# 遍歷每一個node,這里node的類型不同,分別有etree._ElementStringResult(字符),etree._Element(元素),etree._ElementUnicodeResult(字符)
for _node in _comment_data:
# 如果是字符,則直接取出
if isinstance(_node, etree._ElementStringResult):
comment_num = comment_num * 10 + int(_node)
else:
# 如果是span類型,則要去找數據
# span class的attr
span_class_attr_name = _node.attrib["class"]
# 偏移量,以及所處的段
offset, position = css_and_px_dict[span_class_attr_name]
index = abs(int(float(offset) ))
position = abs(int(float(position)))
# 判斷
for key, value in svg_threshold_and_int_dict.iteritems():
if position in value:
threshold = int(math.ceil(index/12))
number = int(key[threshold])
comment_num = comment_num * 10 + number
print comment_num
|
4.結果展示
評論條數數據
其實,其他的我都寫好了,就不貼了


評論具體數據


5.結語
以上就是大眾點評Css反爬破解的全部步驟和部分代碼。
