爬取大眾點評之獲取商家地址
昨天試探性的爬取了大眾點評的數字資訊,但一般我們獲取的資料中,不止是這些數字資訊。在基本資訊裡面,地址也是一個很重要的資料。於是今天嘗試一下怎麼獲取地址。
思路和數字是一樣的,概括就是,通過css檔案裡的偏移量找到class屬性和svg檔案中的漢字的對應關係。唯一的不同在於數字的svg檔案只有一行10個數字,而地址中的svg檔案包含200多個漢字。
地址的class屬性大部分是以bi-開頭的(部分是數字以kj-開頭),點選地址中的字的html程式碼,如下圖:看到右邊有css,背景圖片就是avg檔案。網頁開啟如下圖:
首先通過正則拿到所有文字。然後從css檔案匹配出所有bi-開頭的屬性,這裡在處理上,我本來想根據x、y偏移量排序,這樣不是就和上面的文字位置相對應了,但是最後發現提取出的文字有253個,而bi-開頭的class屬性卻只有246個。也就是說有些文字並不會用到,只是起迷惑的效果。
這樣就只能找x、y偏移量的規律,發現x座標除以14就是這一行的第x/14個字(從0開始),y座標減去7除以30就是第幾行了(從0開始)。而用正則匹配出的所有漢字是8個字串的列表。那麼我可以直接通過第幾行第幾列作為索引找到它對應的漢字。比如四這個漢字就可以通過L[0][2]得到(字串也是可以直接中括號取值的)。這就簡單多了。
下一步這需要從網頁中拿到地址的文字和class屬性了,想這樣的。
<span class="item" itemprop="street-address" id="address"> 口 <span class="bi-UcC4"></span> <span class="bi-MxyS"></span> <span class="bi-sJpz"></span> <span class="bi-0k4r"></span> <span class="kj-QXcp"></span> <span class="bi-DZXt"></span> ( <span class="bi-r15F"></span> 近 <span class="bi-3ZYO"></span> ) </span>
這樣的網頁結構我使用正則和pyquery都無法拿到包含順序的文字和class屬性(哪位大神懂的話還請留言指教),只能想到另一個工具xpath了。
而xpath提取卻很簡單,只需要這樣:
//span[@id="address"]/text() | //span[@id="address"]/span/@class
這樣匹配出來的正好是要的結果,將css屬性換成前面拿到的密碼本解密拼接就得到了地址的資訊。
程式碼如下:
# -*- coding: utf-8 -*-
"""
date: Tue Nov 27 09:48:08 2018
python: Anaconda 3.6.5
author: kanade
email: [email protected]
"""
import requests
import re
class DianPingSpider(object):
'''
獲取商家資訊
'''
def __init__(self, url='http://www.dianping.com/shop/586341'):
html = self.get_index_html(url)
css_html = self.get_css_html(html)
# 數字密碼本class屬性的開頭兩個字母,比如kj
numcb = re.search(r'id="reviewCount".*?>[\s\S]*?class="(\w\w)-\w{4}"', html).group(1)
# 文字密碼本class屬性的開頭兩個字母
charcb = re.search(r'id="address".*?>[\s\S]*?class="(\w\w)-\w{4}"', html).group(1)
self.kjs = self.get_kjs(css_html, numcb)
self.bis = self.get_bis(css_html, charcb)
def get_index_html(self, url):
'''
獲取初始網頁
'''
headers = {
'Host':'www.dianping.com',
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.61 Safari/537.36'
}
resp = requests.get(url, headers=headers)
print(resp.status_code)
html = resp.text
print(len(html))
return html
def get_css_html(self, html):
'''
獲取css檔案的內容
'''
# 從html中提取css檔案的url
regex = re.compile(r'(s3plus\.meituan\.net.*?)\"')
css_url = re.search(regex, html).group(1)
css_url = 'http://' + css_url
# 得到css檔案的內容
resp = requests.get(css_url)
css_html = resp.content.decode('utf-8')
return css_html
def get_kjs(self, css_html, numcb):
'''
獲取kj開頭的class屬性對應顯示的文字字典
'''
# 從css_html中提取kj的svg檔案url
regex = re.compile(r'\[class\^="%s-"\][\s\S]*?url\((.*?)\)'%numcb)
svg_url = re.search(regex, css_html).group(1)
if svg_url.startswith('//'):
svg_url = 'http:' + svg_url
# 得到svg檔案內容
resp = requests.get(svg_url)
svg_html = resp.text
# 從svg內容中提取10位數字
number = re.search(r'\d{10}', svg_html).group()
# 匹配出以kj-開頭的class屬性中的偏移量
regex_kj = re.compile(r'\.(%s-\w{4})[\s\S]*?-(\d+)'%numcb)
kjs = re.findall(regex_kj, css_html)
# 根據偏移量排序
kjs.sort(key=lambda x:int(x[1]))
# 將class屬性其真正顯示的數字組成字典
kjs = {i[0]:number[n] for n,i in enumerate(kjs)}
return kjs
def get_bis(self, css_html, charcb):
'''
獲取bi開頭的class屬性真正顯示的漢字
'''
# 提取相應的svg檔案的url
regex = re.compile(r'\[class\^="%s-"\][\s\S]*?url\((.*?)\)' % charcb)
svg_url = re.search(regex, css_html).group(1)
if svg_url.startswith('//'):
svg_url = 'http:' + svg_url
# 得到svg的內容
resp = requests.get(svg_url)
svg_html = resp.text
# 提取svg檔案中的所有文字資訊
regex = re.compile(r'<text[\s\S]*?>(\w+)<')
content = regex.findall(svg_html)
# 提取css_html中以bi-開頭的class屬性的偏移量
regex = re.compile(r'(%s-\w{4})[\s\S]*?-(\d+)\.0px -(\d+)\.0px' % charcb)
css = regex.findall(css_html)
# 將偏移量轉化為content內容的索引,不要問為什麼,自己試試就知道了。
# 規律而已,並將class屬性和索引內容組成字典
bis = {i[0]:content[int((int(i[2])-7)/30)][int(i[1])//14] for i in css}
return bis
if __name__ == '__main__':
dp = DianPingSpider()
print(dp.bis)
print(dp.kjs)
效果(鍵為css屬性,值為屬性實際顯示出的文字):
{
'jl-QPUK': '皇', 'jl-uZVj': '軍', 'jl-BxqA': '遷', 'jl-Q0Yh': '生',
'jl-MhJh': '秦', 'jl-1sFU': '旗', 'jl-2BCV': '場', 'jl-GNXA': '汾',
'jl-8SkE': '川', 'jl-MX6w': '桂', 'jl-Nuxg': '徽', 'jl-mDhK': '淮',
'jl-y3lT': '放', 'jl-hQuw': '林', 'jl-jjiL': '坊', 'jl-GG4Z': '南',
'jl-lNEy': '衢', 'jl-HSIX': '公', 'jl-ccv6': '港', 'jl-5o0i': '黃',
'jl-1NqB': '湖', 'jl-ZaS9': '內', 'jl-Embx': '二', 'jl-PZ1t': '河',
'jl-dtaB': '無', 'jl-Jzv1': '友', 'jl-MeGl': '安', 'jl-CEZ4': '樂',
'jl-5pF7': '莊', 'jl-cm6w': '遠', 'jl-pl8v': '衡', 'jl-WAY1': '沿',
'jl-mcI4': '家', 'jl-gPN9': '爾', 'jl-kI7q': '封', 'jl-fgKN': '藏',
'jl-cvmd': '誼', 'jl-Ijek': '頭', 'jl-JZxD': '莞', 'jl-Erz3': '保',
'jl-uuVH': '環', 'jl-p6ts': '賓', 'jl-66bs': '海', 'jl-ruFV': '上',
'jl-al8V': '定', 'jl-i8Vt': '廈', 'jl-uTZe': '襄', 'jl-ktdy': '泉',
'jl-fgD1': '春', 'jl-ihvS': '都', 'jl-AFcv': '名', 'jl-ttK5': '德',
'jl-1Xp6': '大', 'jl-Wt6K': '創', 'jl-RTFY': '山', 'jl-JbGA': '七',
'jl-md7e': '街', 'jl-Dvwh': '杭', 'jl-ncXJ': '教', 'jl-m7w5': '康',
'jl-6SNC': '鹽', 'jl-BC4H': '重', 'jl-Mjwk': '市', 'jl-7nIE': '治',
'jl-Quzf': '深', 'jl-ubxa': '遼', 'jl-XhFl': '機', 'jl-3jN3': '雲',
'jl-R4R1': '農', 'jl-5VLj': '感', 'jl-ON6t': '進', 'jl-kV5O': '體',
'jl-DlfW': '成', 'jl-9uni': '擁', 'jl-UHG1': '波', 'jl-YyWW': '站',
'jl-eod5': '興', 'jl-PhSa': '肅', 'jl-kw2v': '新', 'jl-xKe8': '晉',
'jl-KHZ6': '孝', 'jl-JMqW': '遵', 'jl-K7XY': '園', 'jl-Nbcr': '濰',
'jl-ZInM': '學', 'jl-KTMd': '煙', 'jl-UoqP': '夏', 'jl-jxjI': '交',
'jl-v53r': '業', 'jl-jRSW': '文', 'jl-QGW4': '中', 'jl-AJav': '龍',
'jl-FsZi': '凰', 'jl-8UhW': '吉', 'jl-oggh': '迎', 'jl-1kyQ': '昆',
'jl-Ieip': '通', 'jl-Lud6': '汕', 'jl-l4OL': '健', 'jl-CRlk': '連',
'jl-NPF7': '贛', 'jl-UvVw': '烏', 'jl-4zf1': '合', 'jl-VkOh': '六',
'jl-GmNO': '常', 'jl-V9CY': '銀', 'jl-oAq3': '嶽', 'jl-7k9R': '隆',
'jl-I7mE': '五', 'jl-UDxl': '齊', 'jl-d844': '木', 'jl-V9y4': '鞍',
'jl-XSfw': '育', 'jl-9yn1': '臺', 'jl-a31R': '道', 'jl-Rt9k': '村',
'jl-tBgb': '灣', 'jl-GucB': '宜', 'jl-dz4K': '珠', 'jl-I9Uk': '東',
'jl-AkkM': '惠', 'jl-5aVQ': '揚', 'jl-KiFX': '西', 'jl-8WJs': '和',
'jl-hdLu': '區', 'jl-5DTk': '充', 'jl-nNgq': '鎮', 'jl-Wipb': '建',
'jl-4rnc': '錦', 'jl-aARz': '鄉', 'jl-3iem': '陝', 'jl-cTLz': '淄',
'jl-pW2z': '宿', 'jl-zP85': '四', 'jl-an02': '福', 'jl-B28K': '綿',
'jl-TdwG': '祥', 'jl-EV7T': '化', 'jl-PZr5': '臨', 'jl-7Xhj': '門',
'jl-fTL1': '江', 'jl-H9RC': '向', 'jl-kNxX': '信', 'jl-odxj': '紅',
'jl-qpWd': '甘', 'jl-lpCT': '島', 'jl-Ojh1': '徐', 'jl-X0jp': '振',
'jl-fiFx': '曙', 'jl-l9pv': '工', 'jl-Cq50': '鳳', 'jl-Jgk4': '石',
'jl-02WJ': '弄', 'jl-TvEt': '八', 'jl-ZJw2': '風', 'jl-aQL5': '金',
'jl-di6l': '香', 'jl-kQTA': '哈', 'jl-7V9Y': '濟', 'jl-IyA3': '才',
'jl-URwF': '華', 'jl-D2ja': '富', 'jl-lwtp': '省', 'jl-VlC7': '博',
'jl-S8Xy': '梅', 'jl-vkRZ': '寧', 'jl-eD8e': '紹', 'jl-myTb': '光',
'jl-75k8': '慶', 'jl-KMpl': '團', 'jl-QCpk': '開', 'jl-psBD': '太',
'jl-rY7Y': '三', 'jl-0CAN': '沙', 'jl-8ODZ': '民', 'jl-M2Jo': '朝',
'jl-z14X': '湛', 'jl-oaGC': '義', 'jl-Uikz': '威', 'jl-0vd9': '清',
'jl-gPvB': '佛', 'jl-XtjU': '年', 'jl-aHZ4': '錫', 'jl-lzRo': '愛',
'jl-4UnI': '漢', 'jl-ULmU': '蘇', 'jl-dufd': '樓', 'jl-bjnr': '魯',
'jl-KxzU': '昌', 'jl-E2VB': '蒙', 'jl-NM4f': '古', 'jl-vU40': '天',
'jl-3gbV': '州', 'jl-Kmwj': '關', 'jl-GRm3': '長', 'jl-FIc0': '諧',
'jl-lXSW': '武', 'jl-jxay': '花', 'jl-e66k': '勝', 'jl-mHPS': '前',
'jl-J8j6': '茂', 'jl-bvni': '利', 'jl-MdN9': '嘉', 'jl-Dvmk': '京',
'jl-qCuY': '縣', 'jl-ArGp': '韶', 'jl-G2dJ': '主', 'jl-1RGm': '疆',
'jl-vkFr': '黑', 'jl-vpEV': '鄭', 'jl-MEPp': '心', 'jl-mrBy': '城',
'jl-OlhM': '津', 'jl-He0S': '府', 'jl-EoB7': '澳', 'jl-EDnM': '廣',
'jl-vFcQ': '路', 'jl-kcQm': '設', 'jl-ck5T': '結', 'jl-QYLY': '明',
'jl-Vvan': '泰', 'jl-gio3': '青', 'jl-4KAv': '貴', 'jl-FbaI': '圳',
'jl-aNtQ': '解', 'jl-Q71j': '號', 'jl-Tnsf': '十', 'jl-VAc5': '廊',
'jl-olju': '九', 'jl-IBqO': '濱', 'jl-V9dW': '陽', 'jl-NnfK': '層',
'jl-2yXK': '人', 'jl-gOPY': '肇', 'jl-Lo0d': '永', 'jl-P3O9': '幸',
'jl-EL6Z': '源', 'jl-dUux': '岡', 'jl-1Shu': '一', 'jl-CLGR': '洛',
'jl-r2EN': '沈', 'jl-3cC9': '北', 'jl-SMQf': '平', 'jl-roWg': '邢',
'jl-nop4': '浙', 'jl-LOsX': '溫', 'jl-nQA7': '肥', 'zl-FhcV': '9',
'zl-Jvp2': '4', 'zl-gc5M': '2', 'zl-TohQ': '6', 'zl-htaN': '8',
'zl-giSW': '3', 'zl-Bthl': '1', 'zl-tvPf': '7', 'zl-JTyc': '0',
'zl-Cg3x': '5'
}
2018-11-29更新:今天發現商家和電話的css屬性和標籤居然和昨天不一樣,一天變一次也太騷了。
已更新程式碼,可以應對每天的css改變。如果複製程式碼執行報錯,可能已經失效,還請留言,我更新一下。