點評網的反爬設定在我們爬取點評網頁的時候給我們造成了不小的障礙。在網頁上我們看到的是這樣的
網頁上可以看到這家餐廳有1405條評論,人均387。但在分析頁面原始碼的時候,我們卻看不到網頁上的數字,看到是這樣的程式碼
點評網對數字做了處理,一些數字的資訊像評論條數、人均、評分等都做了反爬保護。上面的網頁中評論條數是1405條,但在頁面原始碼中,除了第一個數字1以外,後面的數字我們看不到,都是一些像隨機編碼一樣的css class。
如果我們仔細分析這個css class,其實是不難發現背後的原理的。
通過開發者工具,我們找到這個css的定義,可以看到是下面這樣的
background-image屬性裡面是一個url,我們在瀏覽器裡開啟它,看到它的內容是
lc-mY1i 這個css class裡面是一個background屬性,定義了背景圖片偏移的位置。
所以點評網上顯示數字的原理就是通過設定不同的偏移位置,顯示背景圖片相應位置上的數字。我們可以想象背景圖片的前面有一個視窗,視窗的大小剛好夠顯示一個數字。視窗是固定不動的,背景圖片在後面移動,移動到不同的位置就能顯示這個位置上的數字。
進一步分析背景圖片,我們可以發現,這是一個SVG圖片,圖片中的數字可以在svg的原始碼中看到,如下
理解了原理後,我們用程式碼來實現一下解析的過程。
首先我們從點評的網頁上找出css檔案的url,程式碼如下
def get_css():
url = "http://www.dianping.com/shanghai/ch10"
r = requests.get(url, headers=headers)
content = r.content.decode("utf-8")
matched = re.search(r'href="([^"]+svgtextcss[^"]+)"', content, re.M)
if not matched:
raise Exception("cannot find svgtextcss file")
css_url = matched.group(1)
css_url = fix_url(css_url)
return css_url
複製程式碼
隨後我們從css裡找到背景圖片的路徑,並獲取SVG圖片中的每個數字
def get_svg(css_url):
r = requests.get(css_url, headers=headers)
content = r.content.decode("utf-8")
matched = re.search(r'span\[class\^="lc\-"\].*?background\-image: url\((.*?)\);', content)
if not matched:
raise Exception("cannot find svg file")
svg_url = matched.group(1)
svg_url = fix_url(svg_url)
r = requests.get(svg_url, headers=headers)
content = r.content.decode("utf-8")
matched = re.search(r'class="textStyle">(\d+)</text>', content)
if not matched:
raise Exception("cannot find digits")
digits = list(matched.group(1))
return digits
複製程式碼
這個函式返回一個陣列,陣列的內容是SVG圖片中的所有數字。
對於點評網頁中的用css class表示的數字,我們來解析一下css class和數字之間的對應關係
def get_class_offset(css_url):
r = requests.get(css_url, headers=headers)
content = r.content.decode("utf-8")
matched = re.findall(r'(\.[a-zA-Z0-9-]+)\{background:(\-\d+\.\d+)px', content)
result = {}
for item in matched:
css_class = item[0][1:]
offset = item[1]
result[css_class] = offset
return result
複製程式碼
這個函式返回的是一個字典,它的key是css class的名字,value是css class對應的數字在背景圖片中的偏移量。
接下來,我們以評論條數為例,來獲取點評上一個頁面裡每家餐廳的評論條數。先定義函式,用於獲取評論條數
def get_review_num(page_url, class_offset, digits):
r = requests.get(page_url, headers=headers)
content = r.content.decode("utf-8")
root = etree.HTML(content)
shop_nodes = root.xpath('.//div[@id="shop-all-list"]/ul/li')
for shop_node in shop_nodes:
name_node = shop_node.xpath('.//div[@class="tit"]/a')[0]
name = name_node.attrib["title"]
review_num_node = shop_node.xpath('.//div[@class="comment"]/a[@class="review-num"]/b')[0]
num = 0
if review_num_node.text:
num = num * 10 + int(review_num_node.text)
for digit_node in review_num_node:
css_class = digit_node.attrib["class"]
offset = class_offset[css_class]
index = int((float(offset)+7)/-12)
digit = int(digits[index])
num = num * 10 + digit
last_digit = review_num_node[-1].tail
if last_digit:
num = num * 10 + int(last_digit)
print("restaurant: {}, review num: {}".format(name, num))
複製程式碼
然後呼叫函式,爬一下頁面中每家餐廳的評論條數
css_url = get_css()
digits = get_svg(css_url)
class_offset = get_class_offset(css_url)
url = "http://www.dianping.com/shanghai/ch10/g116"
get_review_num(url, class_offset, digits)
複製程式碼
執行程式碼後,得到如下的結果
restaurant: 1886汽車主題德國餐廳(環宇薈店), review num: 1021
restaurant: Mia Fringe迷芬奇餐廳&酒吧, review num: 152
restaurant: Oyster EXPO江月蠔庭西餐生蠔吧(世博源店), review num: 1405
restaurant: 寶萊納餐廳(陸家嘴店), review num: 7854
restaurant: Pizza Marzano瑪尚諾(港匯店), review num: 7527
restaurant: love&salt牛排館, review num: 86
restaurant: Da Ivo 義大利魔鏡餐廳, review num: 3497
restaurant: Mr Nice好好先生餐廳(月星環球港店), review num: 9052
restaurant: L'ATELIER de Joël Robuchon, review num: 2821
restaurant: Stone Sal 言鹽西餐廳, review num: 62
restaurant: 夏朵花園, review num: 3031
restaurant: 殼裡西餐廳Coquille Seafood Bistro, review num: 322
restaurant: ICHA Chateau Bar & Restaurant(酒吧創意料理), review num: 496
restaurant: 菲斯特花園西餐廳, review num: 655
restaurant: 寶麗嘉酒店Cafe Bellagio(寶麗嘉西餐廳), review num: 598
複製程式碼
對照網頁上的資料,可以看到,餐廳的評論條數都被正確的解析出來了。
本文已同步更新到公眾號【Python與資料分析】,歡迎關注~