昨天试探性的爬取了大众点评的数字信息,但一般我们获取的数据中,不止是这些数字信息。在基本信息里面,地址也是一个很重要的数据。于是今天尝试一下怎么获取地址。
思路和数字是一样的,概括就是,通过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: kanade@blisst.cn
"""
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改变。如果复制代码运行报错,可能已经失效,还请留言,我更新一下。