irpas技术客

基于Python爬虫的基本流程——大众点评商户信息爬取实现【字符库反爬】_SunRains_大众点评 爬虫

大大的周 2679

????????最近接了一个关于大众点评商户信息爬取的需求,一开始以为很简单,所以就欣然接受,心想刚好可以再回顾一下爬虫的技术点。结果在做的过程中,还是碰到了各种各样的问题,所以这里将自己实现的过程以及问题分享出来给大家学习学习。本文是以爬虫的基本流程——发起请求、获取响应的内容、解析内容、保存数据来说明自己当时做的一个思路。

一、 发送请求

? ? ? ? 发送请求也就是向目标站点发送请求,请求可以包含额外的header等信息。所以这里可以很快确定出我们的目标站点即大众点评【北京美食_生活_团购_旅游_电影_优惠券 - 大众点评网】。但是需求里面指出只需要城市为宁波,类型为CH30、CH50、CH55、CH45、CH75、CH95、CH35的所有商铺信息。这里我们可以简单认为这七个类型的汇总页是基本相似的,所以考虑是否可以写出一个,其他类型就可以照搬。

? ? ? ? 确定爬取的地址后,还需要考虑最重要的内容——Header信息。包括Cookie信息、User-Agent的信息。Cookie信息可以直接复制浏览器的Cookie内容,User-Agent也可以这样,但是我这里使用了Fake-UserAgent来随机生成User-Agent信息。

? ? ? ?准备好上面的工作,发送请求这一过程算是基本完成。但是还有一个需要考虑的是否需要使用IP代理进行请求,一般简单的网站是没有进行IP限制的,所以就不需要加入这一步骤。而大众点评这里就严格限制了IP的请求频率。我一开始也没有考虑到这个,以至于爬取了两三百条链接的时候就要频繁的验证,关键是验证后也直接提示操作无效,不能访问。所以只有等过了几个小时后,没有限制才继续进行抓取。所以这里就很有必要使用代理IP。

? ? ? ? ?本想以白嫖IP的想法去找了一些代理IP,但是发现其质量难以保障,所以不得不考虑去买IP代理上的IP。在网上也找了几家,也对比了几家,踩了几个坑,有一家IP代理商注册之后,不能登录,账号是被锁定的,要等他们打电话,问你买代理IP做什么、用于什么地方,我心想自己只是类似于做测试的,问这问那,果断挂断电话,PASS了一家,还有一家说是注册送一定额度的代理,也会打电话咨询加你微信,然后需要实名认证,但是感觉不太好,也就选择了其他的。我最后使用了两个一个是蜻蜓代理和小象代理,前者我做了测试,有200个免费的代理IP供测试,但是实际使用有点儿贵,所以选择了小象搭理5元/天的服务。

二、获取响应的内容

? ? ? ? 这里即指的是服务器正常响应,会得到一个Response,Response的内容即我们需要的内容,类型包括HTML,Json字符串、二进制数据等类型。所以这里需要对Response对象进行判断。判断内容是否是我们想要的内容,比如上面验证的内容其实也是返回成功的,但并不是我们想要的内容,所以这里我们需要对这种类型进行验证,以确保内容的准确性。我在这这里只是对大众点评的验证内容做了简单的校验。

?三、解析内容

? ? ? ? 得到的内容是HTML,可以利用正则表达式、页面解析库进行解析。有的也可能是Json,则直接转为Json对象进行解析,二进制的数据可以先保存后再做进一步的处理。解析内容可以算的上是最重要的一个环节,我踩过的坑也大部分都是在这里。

? ? ? ? 在这里解析内容我使用的是Xpath进行解析,所以一般都是先打开浏览器F12,找到自己想要内容的对应位置,然后右键获取对应的Xpath值。

?????????对于列表页面基本上都是统一的,但是详情页就是五花八门的。不同类型的详情页面可能会不同(如下面两张图),所以我们需要针对于每一种类型都要找到对应内容的Xpath,并以此进行解析。并每一个需要判断自己获取的对象是不是为空,所以就有了非常麻烦的判断。

?

? ? ? ? ?页面不同的问题其实还算好解决,但是有的页面地址、电话、标题中的内容会被进行加密,我们F12看到的内容就是下图所示。如果用Xpath直接获取text的方式获取的内容与想要的内容就不一致。所以就需要进行分析。

? ? ? ?在网上看了一些博客后,大概了解了一下这里所作的一个反爬思路,首先是大众点评会将部分字体进行不同格式的编码生成一个对应的woff文件,然后会在一个CSS文件引用这些文件,HTML标签页面会根据Class名称引用对应的woff文件并显示具体的内容。

????????就拿电话的信息来说,浏览器F12这里看到的每一个电话数字对应一个class为num的d标签,num指向css文件指定的Font-family为“PingFangSC-Reqular-num”。这里对于d标签中的内容其实是<d class="num">&#xf440;</d>这种内容,而Font-family这个字体对应CSS文件里面。

???????找到对应的字体代开可以看到如下的内容,然后根据<d>标签总内容的后四位和字体中的值顺序进行比对,比对成功则是对应标签真实值。但是这一步前提是建立一个下面字体的对照表。

? ? ? ? ?针对于上面这种字符库加密的方式解析,总的思路,首先找到svgtextcss文件所在位置,然后解析css内容找到对应字体的内容,下载到本地,然后利用TTFont进行字体读取以及对照表。然后对照获取真实值。实现过程如下:

class FontUtils(object): @classmethod def down_font(cls, html_text): html = etree.HTML(html_text) # 获取所有link标签的href属性,得到svgtextcss所在的位置 link_tag = html.xpath("//link/@href") svg_text_css_href = "" for item in link_tag: if item.find("svgtextcss") > -1: svg_text_css_href = item if len(svg_text_css_href) == 0: raise RequestException("获取svg地址失败") svg_href = "http:{}".format(svg_text_css_href) svg_file_name = svg_href.split("/") svg_file_name = svg_file_name[len(svg_file_name) - 1] # 判断svg_css是否存在 if os.path.isfile("./css/{}".format(svg_file_name)): return # 不存在,则下载获取 svg_content = RequestUtils.download_file_text(svg_href) with open("./css/{}".format(svg_file_name), "w", encoding="utf-8") as file: file.write(svg_content) # 下载字体 s = re.findall(r'\{(.*?)\}', svg_content) for item in s: # 解析字体 font_family = re.findall(r'font-family: "(.*?)"', item) if len(font_family) == 0: continue href = re.findall(r',url\("(.*?)"\);', item) file_path_name = "./font/{}.woff".format(font_family[0].split("-")[-1]) file_href = "https:{}".format(href[0]) # 下载 content = RequestUtils.download_file_content(file_href) with open(file_path_name, "wb") as file: file.write(content) @classmethod def get_font_word(cls, name, key): tag = TTFont("./font/{}.woff".format(name)) order_list = tag.getGlyphOrder() index = order_list.index(key) """ 获取woff文本对照 :return: """ woff_str = ''' 1234567890店中美家馆 小车大市公酒行国品发电金心业商司超 生装园场食有新限天面工服海华水房饰 城乐汽香部利子老艺花专东肉菜学福饭 人百餐茶务通味所山区门药银农龙停尚 安广鑫一容动南具源兴鲜记时机烤 文康信果阳理锅宝达地儿衣特产西批坊 州牛佳化五米修爱北养卖建材三会鸡室 红站德王光名丽油院堂烧江社合星货型 村自科快便日民营和活童明器烟育宾精 屋经居庄石顺林尔县手厅销用好客火雅 盛体旅之鞋辣作粉包楼校鱼平彩上 吧保永万物教吃设医正造丰健点汤网庆 技斯洗料配汇木缘加麻联卫川泰色世方 寓风幼羊烫来高厂兰阿贝皮全女拉成云 维贸道术运都口博河瑞宏京际路祥青镇 厨培力惠连马鸿钢训影甲助窗布富牌头 四多妆吉苑沙恒隆春干饼氏里二管 诚制售嘉长轩杂副清计黄讯太鸭号街交 与叉附近层旁对巷栋环省桥湖段乡厦府 铺内侧元购前幢滨处向座下臬凤港开关 景泉塘放昌线湾政步宁解白田町溪十八 古双胜本单同九迎第台玉锦底后七斜期 武岭松角纪朝峰六振珠局岗洲横边 济井办汉代临弄团外塔杨铁浦字年岛陵 原梅进荣友虹央桂沿事津凯莲丁秀柳集 紫旗张谷的是不了很还个也这我就在以 可到错没去过感次要比觉看得说常真们 但最喜哈么别位能较境非为欢然他挺着 价那意种想出员两推做排实分间甜 度起满给热完格荐喝等其再几只现朋候 样直而买于般豆量选奶打每评少算又因 情找些份置适什蛋师气你姐棒试总定啊 足级整带虾如态且尝主话强当更板知己 无酸让入啦式笑赞片酱差像提队走嫩才 刚午接重串回晚微周值费性桌拍跟 块调糕 ''' woffs = [i for i in woff_str if i != '\n' and i != ' '] word = woffs[index - 2] return word @classmethod def translate_tag(cls, html, tag_xpath): children_tag_div = html.xpath(tag_xpath + "/*") children_tag_dict = {} # 翻译内容 for item in children_tag_div: if item.tag != "e" and item.tag != "d": continue class_name = item.xpath("./@class")[0] item_value = item.xpath("./text()")[0] if item_value.startswith("*") and item_value.endswith("*"): children_tag_dict[item_value] = cls.get_font_word(class_name, "uni" + item_value.strip("*")) else: children_tag_dict[item_value] = item_value # 和现有的组合 char_arr = [] for item_char in html.xpath(tag_xpath + "//text()"): if item_char in children_tag_dict.keys(): char_arr.append(children_tag_dict[item_char.strip()].replace("\\n", "")) else: char_arr.append(item_char.strip().replace("\\n", "")) return "".join(char_arr) 四、保存数据

? ? ? ? 解析后的数据肯定需要保存到本地或者存入数据库,而这里需要存入excel,也就是使用了xlwt进行Excel的读写。

运行结果图

五、总结与分析

? ? ? ? 保存数据完成后,整个爬虫也就完成,我们最终的目的也就实现了。但是完成需要对这个爬虫案列进行一个总结,虽然效果是实现了,但是存在着以下的不足:

????????第一,保存数据时是才用的爬取完成后就立刻写入,但是写入的时候就涉及到I/O操作,比较耗时,因为获取的是代理IP,每一个IP都有一定的时效性,一个I/O操作完成本来可以访问五个页面的IP现在只能访问两个,所以应该考虑,将保存数据与请求访问分开。可以先将请求的内容保存到本地,再来解析。

? ? ? ? 第二,单线程操作,虽然代码逻辑简单,但是执行耗时。可以考虑多线程爬取,只是此时需要考虑代理IP分配、爬取页面的分配等。

? ? ? ? 第三,翻译加密标签内容时,每次都要重新读取woff文件,建立对应的对照表,这一过程是十分耗时的,所以考虑优化这一部分,将这一结果固定下来。

? ? ? ? 第四,考虑程序中断、异常处理后,爬虫现场的保存,不然每一次都要重复爬取,造成不必要的资源浪费,也有可能造成数据的重复。

参考文章

①? 爬虫入门经典(二十一) | 破解CSS加密之爬取大众点评?

②? python爬虫—关于大众点评数据的爬取!

代码地址

DianPingSpider: 大众点评商户信息爬虫,包括名称、电话、地址、优惠活动。


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #大众点评 #爬虫 # #发送请求