前言

近期,从Boss那里领取了一份爬数据的任务,刚开始接到任务的时候,感觉应该很简单,页面是静态页面,不用js渲染,也不用自己构造请求参数,就是个从网页文本中用标签选择器咔咔一顿乱选,最后整理存入mongoDB即可,导出csv,完成任务。这本来是我心目中完美的一套流程,但是,随着更加深入的进入到工作节奏中之后,我才发现事情并没有那么简单~~~(大众点评的前端,你真可爱)

前期准备

古人云:工欲善其事,必先利其器。一般网络爬虫,比较经典的几个库是必须的,例如:lxmlrequestsBeautifulSoup,先甭说其他的,先装吧

1
2
3
pip3 install lxml
pip3 install requests
pip3 install beautifulsoup4

在准备好我们的利器之后,接下来肯定就是开始搞事情~~~

开搞第一波

熟练的在浏览器中输入http://www.dianping.com,然后随便打开一个店,比如这个

1565527644309

熟练的点击登录,拿手机扫码登录,出现上图~~~

随着下拉页面,我们终于看到了我们所需要的评论数据,比如这样:

1565527767039

来,摁一下F12,右击检查来瞧瞧这些可爱的小宝贝,到底在html里面是怎么存在的,此时发出了志在必得的嘲笑声~~~哈哈哈哈哈

然后~~~

1565527909029

这是什么??此刻黑人问号脸??在页面上不是TMD显示是字吗?这个svgmtsi是什么标签,我的网页设计白学了?我记着我当时学习挺认真的啊?大兵老师教的挺好呀????????????

img

第一次尝试,失败!Game Over!

第二次尝试

在进行第一次尝试之后,发现大众点评这个网站并不是用普通方式渲染的,是用特定的标签进行渲染上去的,这个标签到底是什么呢?让我很是费解,从来没见过,我该如何下手?既然是不认识的东西,那么看看它的属性?在CSS是怎样表示的。

1565528442040

看了一下这个标签,在CSS中只有一个background属性,给了两个坐标,难道字是用图片拼的?带着怀疑的心情,看一看这个background究竟是何方神圣,打开那个url一看,是这样的情况:

1565529438230

看到了这一个个熟悉的字,果然是通过图片渲染到页面上的,但是,另外一个难题出现了,这个字究竟是怎么对应上去的,此时还有一个信息没有用,那就是上面CSS发现的坐标值,看一看这个svg的源码吧,看看有没有什么发现,打开源码,是这样的~~~

1565529694884

看到了,x和y的值,是不是跟上面的background有一定的关系,此时发挥出小时候做找规律数学题的技能,找一找规律~~~

1565532374180

我们来看一下这个字的坐标:-434-1512,在svg源码中搜索这个字

1565533409422

字在这一行中是第32个字,同时这一样的y坐标是1535,在CSS中它的属性y坐标是-1512,如果将坐标取正值,正式坐标差23,推测一下,y坐标计算是通过background属性y取正加23得到,我们验证一下其他字,发现也是这个规律,至此,我们推测出,y坐标的对应关系;现在还剩下最后一个难题,x的坐标该怎么对应?此时发现svg上面有这么一个属性:

1565533863617

字号是14px,是不是字数*14就等于x坐标了呢?求证一下,年是第32个字,但是31 * 14 = 434,所以大胆猜测一下,x坐标是这个字在这一行的(第几位-1)* 14所得,后来发现其他字也是这样,推测正确。

第二次尝试~~~成功!

获得结论

每个标签的background属性对应着svg中的位置,首先计算过程是将x、y取正,用x / 14 所得的值+1,就是这个标签所代表字在这一行的第几位,用 y + 23 就是带这个标签代表字在哪一行

获取网页源码

接下来要做的,就是通过正常的get请求,去获取评论页的源码,在经过几次的尝试之后,发现这么一个问题,每次请求如果用一个请求头的话,最多你只能拿到30页左右的评论数据,在想继续拿到就会被封锁,即使你传入Cookie值,也无济于事

所以你需要使用一个第三方的库:fake_useragent

1
pip3 install fake_useragent

使用示例:

1
2
3
from fake_useragent import UserAgent
ua = UserAgent(verify_ssl=False)
ua.random #这里会生成一个随机的浏览器请求头

第二步,在收集网页数据的过程中,每次请求的间隔不要太短,每次请求的过程中还可能触发验证机制,你需要在浏览器端进行手动验证,方可继续使用访问,每次请求评论页的Referer是上一页的网址,意思就是告诉大众点评,你是一页一页评论连续看的,并不是从第一页一直调到其他页,下面放一下源码:

get_data.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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,在网址中也可以看到,例如这个:1565535039899

第二个参数,是你要爬到第几页

第三个参数,是你从浏览器中复制的Cookie

1565535133342

开始爬取

代码流程如下:

读取网页源码—>从源码中获取CSS文件URL—>从CSS文件中获取到SVG文件的URL—>获取SVG的内容—>选取评论标签—>解析标签SVG的URL—>解析标签的class—>获取坐标—>从SVG中对应字—>保存数据

在这里贴出来几个关键函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
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)

结果展示

dazhong

1565535819724

Tips

在与大众点评斗志斗勇的过程中,发现大众点评的CSS加密机制是一天两换的,上面的那个加密机制只是白天的一种,如果你访问频率过多,大众点评会自动触发另外一套加密机制,另外一套加密机制将会在下期进行述说,敬请期待。近期,我会把代码整理并开源至Github,欢迎各位的指正与指导!

EmailTyrantLucifer@linuxstudy.cn

个人博客https://www.tyrantlucifer.com