前言
近期,从Boss那里领取了一份爬数据的任务,刚开始接到任务的时候,感觉应该很简单,页面是静态页面,不用js渲染,也不用自己构造请求参数,就是个从网页文本中用标签选择器咔咔一顿乱选,最后整理存入mongoDB即可,导出csv,完成任务。这本来是我心目中完美的一套流程,但是,随着更加深入的进入到工作节奏中之后,我才发现事情并没有那么简单~~~(大众点评的前端,你真可爱)
前期准备
古人云:工欲善其事,必先利其器。一般网络爬虫,比较经典的几个库是必须的,例如:lxml
、 requests
、 BeautifulSoup
,先甭说其他的,先装吧
pip3 install lxml
pip3 install requests
pip3 install beautifulsoup4
在准备好我们的利器之后,接下来肯定就是开始搞事情~~~
开搞第一波
熟练的在浏览器中输入
http://www.dianping.com
,然后随便打开一个店,比如这个
熟练的点击登录,拿手机扫码登录,出现上图~~~ 随着下拉页面,我们终于看到了我们所需要的评论数据,比如这样:
来,摁一下F12,右击检查来瞧瞧这些可爱的小宝贝,到底在html里面是怎么存在的,此时发出了志在必得的嘲笑声~~~哈哈哈哈哈 然后~~~
这是什么??此刻黑人问号脸??在页面上不是显示是字吗?这个 svgmtsi
是什么标签,我的网页设计白学了?我记着我当时学习挺认真的啊?大兵老师教的挺好呀????????????
第一次尝试,失败!Game Over!
第二次尝试
在进行第一次尝试之后,发现大众点评这个网站并不是用普通方式渲染的,是用特定的标签进行渲染上去的,这个标签到底是什么呢?让我很是费解,从来没见过,我该如何下手?既然是不认识的东西,那么看看它的属性?在CSS是怎样表示的。
看了一下这个标签,在CSS中只有一个 background
属性,给了两个坐标,难道字是用图片拼的?带着怀疑的心情,看一看这个 background
究竟是何方神圣,打开那个url一看,是这样的情况:
看到了这一个个熟悉的字,果然是通过图片渲染到页面上的,但是,另外一个难题出现了,这个字究竟是怎么对应上去的,此时还有一个信息没有用,那就是上面CSS发现的坐标值,看一看这个svg的源码吧,看看有没有什么发现,打开源码,是这样的~~~
看到了,x和y的值,是不是跟上面的 background
有一定的关系,此时发挥出小时候做找规律数学题的技能,找一找规律~~~
我们来看一下 年
这个字的坐标:-434
、 -1512
,在svg源码中搜索这个字
年
字在这一行中是第32个字,同时这一样的y坐标是1535,在CSS中它的属性y坐标是-1512,如果将坐标取正值,正式坐标差23,推测一下,y坐标计算是通过background属性y取正加23得到,我们验证一下其他字,发现也是这个规律,至此,我们推测出,y坐标的对应关系;现在还剩下最后一个难题,x的坐标该怎么对应?此时发现svg上面有这么一个属性:
字号是14px,是不是字数*14就等于x坐标了呢?求证一下,年是第32个字,但是31 * 14 = 434,所以大胆猜测一下,x坐标是这个字在这一行的(第几位-1)* 14所得,后来发现其他字也是这样,推测正确。 第二次尝试~~~成功!
获得结论
每个标签的background属性对应着svg中的位置,首先计算过程是将x、y取正,用x / 14 所得的值 1,就是这个标签所代表字在这一行的第几位,用 y 23 就是带这个标签代表字在哪一行
获取网页源码
接下来要做的,就是通过正常的get请求,去获取评论页的源码,在经过几次的尝试之后,发现这么一个问题,每次请求如果用一个请求头的话,最多你只能拿到30页左右的评论数据,在想继续拿到就会被封锁,即使你传入Cookie值,也无济于事
所以你需要使用一个第三方的库:fake_useragent
pip3 install fake_useragent
使用示例:
代码语言:javascript复制from fake_useragent import UserAgent
ua = UserAgent(verify_ssl=False)
ua.random #这里会生成一个随机的浏览器请求头
第二步,在收集网页数据的过程中,每次请求的间隔不要太短,每次请求的过程中还可能触发验证机制,你需要在浏览器端进行手动验证,方可继续使用访问,每次请求评论页的Referer是上一页的网址,意思就是告诉大众点评,你是一页一页评论连续看的,并不是从第一页一直调到其他页,下面放一下源码:
get_data.py
import requests
import time
import re
import sys
from fake_useragent import UserAgent
ua = UserAgent(verify_ssl=False)
url = 'http://www.dianping.com/shop/' sys.argv[1] '/review_all'
for i in range(2, int(sys.argv[2])):
headers = {
'User-Agent': ua.random,
'Referer': url '/p' str(i-1),
'Cookie': sys.argv[3]
}
u = url '/p' str(i)
result = requests.get(u, headers=headers).text
a = re.findall('<p class="not-found-words">抱歉!页面无法访问......</p>', result, re.S)
b = re.findall("<div class='logo' id='logo'>验证中心</div>", result, re.S)
if a:
print('IP被封,死心了吧')
temp = input()
elif b:
print('去浏览器进行手工验证')
temp = input()
else:
with open(str(i) '.html', 'w', encoding='utf-8') as file:
file.write(result)
print(u '已经下载完毕')
time.sleep(5)
在使用get_data.py的时候需要传入三个参数,第一个是你要爬取的店铺的id,在网址中也可以看到,例如这个:
第二个参数,是你要爬到第几页
第三个参数,是你从浏览器中复制的Cookie
开始爬取
代码流程如下:
读取网页源码--->从源码中获取CSS文件URL--->从CSS文件中获取到SVG文件的URL--->获取SVG的内容--->选取评论标签--->解析标签SVG的URL--->解析标签的class--->获取坐标--->从SVG中对应字--->保存数据
在这里贴出来几个关键函数:
代码语言:javascript复制def get_Word_Point(className, CSSContent):
point = re.findall(className '{background:-(.*?).0px.*?-(.*?).0px', CSSContent, re.S)
x = int(int(point[0][0])/14)
y = int(point[0][1]) 23
return x, y
def get_Word_Content(SVGUrl, x, y, SVG_dic):
SVGContent = SVG_dic[SVGUrl]
result = re.findall('<text x="0" y="' str(y) '">(.*?)</text>', SVGContent, re.S)
if result:
return result[0][x]
else:
return get_Word_Content_B(SVGUrl, x, y, SVG_dic)
结果展示
Tips
在与大众点评斗志斗勇的过程中,发现大众点评的CSS加密机制是一天两换的,上面的那个加密机制只是白天的一种,如果你访问频率过多,大众点评会自动触发另外一套加密机制,另外一套加密机制将会在下期进行述说,敬请期待。