GuoXin Li's Blog

Spider for a Book "Secrets from the New Science of Expertise" comments on JD....

字数统计: 4.2k阅读时长: 16 min
2019/08/06 Share

爬取京东《刻意练习》图书评论并生成图词

前言:

本文主要为爬虫入门篇,通过简单的 requests 模块,爬取京东销售的图书《刻意练习》的评论,并生成云图词,体验爬虫的小乐趣。

实现的目的

《刻意练习》这本书是一本非常棒的书,作者通过长时间的观察和亲身实践,研究和讲述学习的过程,以及从新手到大师 master 的必经之路,和刻意练习之方法。

如果非常喜欢这本书,想看一下买这本书的人对于这本书的评价是什么样的,当然可以自己去看评论区,但是只能草略的看,想全面的评论统计,那么就需要爬虫出马了。

通过爬虫可以快速地,大量地爬取到有关这本图书的几乎所有的评论,然后统计所有评论中的关键词,生成词图,达到我们快速了解购买这本书的读者的评价的目的。

上手

首先,我们需要打开京东关于这本书的评论,这很简单,用浏览器我们可以办得到(推荐使用 Chrome 浏览器,可能会更加方便)

Screen Shot 2019-08-02 at 00.14.33

我们点击中间的商品评价,所有的评论便都在下面了。

第一行代码

本次爬取数据,使用的是 Python 的一个库——Requests,在这里我们只需要明白它是一个特别强大的库就可以了,如果想要了解更多关于Requests的内容,我们只需要用搜索引擎搜索它,然后详细仔细的查看和研究,本文我们变用变学习Requests 的功能。

导入 Requests 库
1
import requests

上面这第一行代码意思是将Requests库导入当前python文件,只有导入后才能使用(Python 有很多的库文件,我们用哪个就需要提前导入哪个)

定义评论链接
1
url = "https://sclub.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98vv2783&productId=11990777&score=0&sortType=5&page=0&pageSize=10&isShadowSku=0&rid=0&fold=1"

这段代码定义一个 url 变量,并将评论链接赋值给url这个变量,但是这个评论链接是从哪里找到的呢?有了这个疑问,这也是本次教程最重要的部分。

找到评论链接

要想获得所有评论,我们要有一个概念,1. 其实评论肯定都存储在京东的服务器上,2. 当我们点击评论的时候,浏览器向服务器索要评论内容,3. 服务器会给出一个评论链接,4. 浏览器通过这个评论链接便能查看所有的评论

好,我们可以理解为,有一条线,这条线牵引着所有的评论内容。接下来我们的目的就是找到这条线。

打开浏览器开发模式

按 F12(或者 fn键+F12),再或者在评论区的当前页面 右击 然后点击 检查(inspect) 选项,开发者模式就被我们调出来了。具体如下图:

Screen Shot 2019-08-02 at 00.22.48

image-20190802002435809

出现这个之后一定要先做一件事情:点击下图标号1,开发者框中的「Network」按钮,然后再将页面进行刷新(按 F5,或者如图点击标号2的刷新图标)如下图:

image-20190802004144553

想要找到评论我们需要全文搜索评论元素,当然不要忘记了,用过 Word我们肯定知道,Control键+F 是可以全文进行搜索的,在这里当然也管用。

我们试一下:先复制第一条评论“非常不错啊!有理论,更有实践,没节都有些tips可以使用。真好。强烈推荐。”

image-20190802002742769

然后点击一下开发者区域(也就是我们之前按 F12 之后出现的区域),按下 Control + F(Mac 下面是 Command + F),会在左边出现一个搜索框:我们将复制的评论粘贴在搜索框中,并按下回车键

image-20190802004517867

我们点击搜索出现的第一条内容,如上图箭头所指示,会在右边出现下面的内容:

image-20190802004710937

我们的目的是要找链接,观察到有一个headers,没错,链接就藏在里面,点击查看:

image-20190802004935850

我们稍微简单解释一下几条信息:

  • Request URL:URL代表的是Uniform Resource Locator,统一资源定位符,它是WWW的统一资源定位标志,就是指网络地址。比如说“www.baidu.com”这就是一个 URL

    那很显然了,这个URL请求的链接就是服务器返回给浏览器的评论的链接。

  • Request Method:GET,这个代表的是 Request 这个请求的方法。如果我们搜索 Request 这个关键词,就会发现 Request 其实是客户端用来发送请求的一个对象,而 Request 对象有好几种方法,GET 是其中一种,Request 利用这些方法向服务器发送请求。

    这个在这里不重要,简单了解即可。

  • Status Code:200。字面含义就很清楚了,状态编码,我们用200来代表,请求成功,当然,还有一些其他的编码,比如我们所熟悉的 404 网页不存在,或者不到网页(如果你没有碰到过404的话,没关系,你可以在浏览器中输入”www.google.com”,404就会出现的)

真正的开始

别忘了,我们的主要目的是干什么——找到评论链接,事实上我们已经找到了,就在上面的 Request URL中,我们不妨复制下来看看它有什么特征

https://sclub.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98vv2788&productId=11990777&score=0&sortType=5&page=0&pageSize=10&isShadowSku=0&fold=1”

嚯,这实在是太难了,如果我们之前从没有接触过的话,我们几乎不可能看出它有什么重要特征。

不要紧,事实上,这只是第一个评论页面的链接,我们不妨再找一下第二页评论的链接,然后我们来找区别,这样就会有一些特征出现。

第二页评论:我们先点击评论的数字 2

image-20190802010558366

然后按照上面所说的方法,复制当前页面的第一个评论,然后去开发者页面中的搜索框中搜索,然后点击出现的第一条内容,然后再在右边查看 headers 中的 Request URL 的内容,第二个链接:

https://sclub.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98vv2788&productId=11990777&score=0&sortType=5&page=1&pageSize=10&isShadowSku=0&rid=0&fold=1“

好了,开始找不同,我们用文本对比网页(搜索引擎搜索“文本对比”即可出现相关链接)会很快发现,有一个明显的异同点:

image-20190802011421852

为什么是 0 和 1 呢?如果我们再看第三页会怎样呢?

事实上,当第三页,这个二者有不同的地方就变成了数字2,好了,这一切都联系起来了。从 0 开始代表了第一页,之后每增加一页,数字加1。

知道了上面这些,意味着我们可以真正开始大量爬取评论数据了。

爬取第一页评论

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests

def comment_spider():
url ="https://sclub.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98vv2783&productId=11990777&score=0&sortType=5&page=1&pageSize=10&isShadowSku=0&rid=0&fold=1"

try:
r = requests.get(url)
print(r.status_code)
print("评论数据:"+r.text[:100])
except:
print("爬取失败")

if __name__ == '__main__':
comment_spider()

上面我们定义了一个函数comment_spider( ) ,这个函数里面的URL是我们上面费力找到的。

然后,我们通过使用try,尝试对URL进行数据爬取:

1
r = requests.get(url)

这是我们利用Requests发起一个GET请求,并把请求到的数据赋值给r。

1
print(r.status_code)

我们利用r.status_code来查看,请求内容是否成功,我们在之前说过,如果返回200,代表请求成功,如果返回404,证明访问的内容不存在。当然,还有可能返回到其他的编码,但我们想要的显然是 200

如果try语句之后的内容执行失败,我们就会执行except后面的内容。

1
2
if __name__ == '__main__':
comment_spider()

上面的代码,即为一个主函数执行函数入口,在主函数里我们执行编写的comment__spider() 函数

执行一下看一下

s

可以看到,返回 200 访问成功的状态编码,意味着我们的URL访问没有问题,但是为什么没有任何的评论数据显示呢?

事实上,这是我们请求的时候,服务器核验我们出了差错,这是基础的反爬虫措施,我们返回开发者页面,进行查看。

image-20190802183708060

有一个Requests Headers,这是一个请求头,我们需要在利用requests.get方法发起请求的时候,把这个请求头加上,这样服务器就能够正常返回我们想要的内容。

我们需要设置一个字典:

1
kv = {'user-agent': 'Mozilla/5.0','Referer':'https://item.jd.com/11990777.html'}

这里面包含的user-agent代表发起请求的浏览器信息,Refeer用于标识请求的来源

然后我们修改requests.get请求,把我们的请求头加上:

1
r = requests.get(url,headers = kv)

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

import requests


def comment_spider():
url = "https://sclub.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98vv2783&productId=11990777&score=0&sortType=5&page=0&pageSize=10&isShadowSku=0&rid=0&fold=1"
kv = {'user-agent': 'Mozilla/5.0','Referer':'https://item.jd.com/11990777.html'}
try:
r = requests.get(url,headers = kv)
print(r.status_code)
print("评论数据:"+r.text[:100])
except:
print("爬取失败")

if __name__ == '__main__':
comment_spider()

image-20190802185102214

可以看到打印的内容,我们可以获取到想要的想要的评论数据。

值得一提的小细节

我们通过查看打印内容,真正的JSON数据是{ }里面的内容,也就是,去掉前面的“fetchJSON_comment98vv2783( “ 和 后面的 “); “ 的内容。

我们用切片的方法,来获取这段JSON数据。

1
r_json_str = r.text[26:-2]

这段代码定义一个 r_json_str 的变量,其中[26:-2] 表示将r.text 内容的第26个到倒数第2个字符切下来。

数据分析与提取

我们上面获得到的数据,其实是JSON数据,至于JSON是什么,我们现在只需要知道,它是一种数据交换格式,可以用来传递一些数据。

我们通过开发者模式中的Preview便可以稍微查看一下有什么数据,也可以百度搜索 JSON解析链接后,将 JSON 数据粘贴进去查看解析数据。

这里我们利用 ChromePreview 功能查看一下。

image-20190802185550522

可以发现有一个comment,这个数据其实是一个字典格式。

python 有一个库,可以方便的查看字典格式,将整个层次分割开来。

我们先利用 import 导入这个库

1
import pprint

然后下面的代码可以查看 JSON 解析数据字典格式的层次结构

1
2
data = json.loads(r.text[26:-2])
pprint.pprint(data)

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests
import pprint
import json

def comment_spider():
url = "https://sclub.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98vv2783&productId=11990777&score=0&sortType=5&page=0&pageSize=10&isShadowSku=0&rid=0&fold=1"
kv = {'user-agent': 'Mozilla/5.0','Referer':'https://item.jd.com/11990777.html'}
try:
r = requests.get(url,headers = kv,timeout = 100)
data = json.loads(r.text[26:-2])
pprint.pprint(data)
except:
print("爬取失败")

if __name__ == '__main__':
comment_spider()

打印出的字典结构:

image-20190805222738285

可以看到评论内容存在content对象中,所以我们只需要循环出所有 comments 中的 content 对象中内容,就能将所有的评论取出来。

然后我们尝试把评论打印出来

image-20190805223742596

完整代码:

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

import requests
import pprint
import json

def comment_spider():
url = "https://sclub.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98vv2783&productId=11990777&score=0&sortType=5&page=0&pageSize=10&isShadowSku=0&rid=0&fold=1"
kv = {'user-agent': 'Mozilla/5.0','Referer':'https://item.jd.com/11990777.html'}
try:
r = requests.get(url,headers = kv,timeout = 100)
#获取到json的字符串
r_json_str = r.text[26:-2]
#字符串转换为json对象
r_json_obj = json.loads(r_json_str)
#获取到json字典中的comments对象
r_json_comments = r_json_obj['comments']
#循环打印出所有的评论数据
for comment in r_json_comments:
print(comment['content'])

except:
print("爬取失败")

if __name__ == '__main__':
comment_spider()

翻页处理,打印多页评论

值得注意的是,我们上面只能打印出第一页的评论数据,这是因为我们的 URL 链接只是第一页的数据链接。

但我们前面已经分析过每个页码的 URL 差别其实就是 page=0 这个地方的不同,如下图:

image-20190802011421852

所以我们只需要修改有差别的地方,便可以获取多页的数据。

怎样修改呢?

image-20190805230426523

首先改造一下 comment_spider 函数,使其拥有一个默认参数 page = 0,也就是默认情况下爬取第1页内容,但为什么是数字0呢?这是因为编程习惯上使用0作为开始的下标。

上面这段代码,还包含一个 数据保存 的功能,其中的 FILE_PATH 就是文件路径,我们需要在开头的位置提前定义一下:

1
FILE_PATH = 'book_comments.txt'

然后,我们重复执行这个函数,每次将其参数 page 进行 +1 操作,就可以实现爬取多页数据,怎样实现这个重复呢?对,我们可以再定义一个函数来执行它啊!

我们定义 loop_spider() 函数来重复执行 comment_spider(page = 0) 这个函数,这样就达到目的了。

image-20190805231846821

在这里我们首先用 pythonOS 模块进行判断文件是否存在,如果存在先删除掉。然后再利用 for 循环执行 comment_spider(page = 0) 这个函数。在这里我们先执行10次。

还注意到,我们使用了 time 这个 python 库,这一行代码是用来进行延时处理的,这样做的目的是为了不让我们的爬虫操作太频繁,而被系统封掉 IP

完整代码:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
import os
import requests
import pprint
import json
import time
import random

FILE_PATH = 'book_comments.txt'

def comment_spider(page = 0):
url = 'https://sclub.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98vv2783&' \
'productId=11990777&score=0&sortType=5&page=%s&pageSize=10&isShadowSku=0&rid=0&fold=1'%page
kv = {'user-agent': 'Mozilla/5.0','Referer':'https://item.jd.com/11990777.html'}
try:
r = requests.get(url,headers = kv,timeout = 100)
#获取到json的字符串
r_json_str = r.text[26:-2]
#字符串转换为json对象
r_json_obj = json.loads(r_json_str)
#获取到json字典中的comments对象
r_json_comments = r_json_obj['comments']

#循环打印出所有的评论数据, 并保存至指定文件
for comment in r_json_comments:
with open(FILE_PATH,'a+') as f:
f.write(comment['content']+'\n')
print(comment['content'])
except:
print("爬取失败")

def loop_spider():
'''
保存爬取数据
'''
if os._exists("FILE_PATH"):
os.remove("FILE_PATH")
for i in range(100):
comment_spider(i)
time.sleep(random.random() * 5)

if __name__ == '__main__':
#comment_spider()
loop_spider()

这样一来,我们就能获取到很多评论数据,默认爬取10页评论,如果想要爬取更多,更改 loop_spider() 函数中的参数就能实现我们想要爬取的页数,比如改为50页:只需修改最后的主函数 loop_spider()loop_spider(50)

生成云词

生成云词我们定义两个函数,一个是用来获取,评论内容,并进行分词处理;另一个用来生成云词。

image-20190805232913819

其中我们需要设置两个参数

1
2
WC_MASK_IMG = 'ky.jpg'  #云词的背景图片
WC_FONT_PATH = '/Library/Fonts/Songti.ttc' #云词的默认字体

最终的完整代码:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import os
import time
import requests
import random
import json
import pprint
import jieba
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
from wordcloud import WordCloud


kybook_path = 'kybook.txt'
WC_MASK_IMG = 'ky.jpg'
WC_FONT_PATH = '/Library/Fonts/Songti.ttc'

def comment_spider(page = 0):
url = "https://sclub.jd.com/comment/productPageComments.action?callback=fetchJSON_comment98vv2783&productId=11990777&score=0&sortType=5&page=%s&pageSize=10&isShadowSku=0&rid=0&fold=1"%page
kv = {'user-agent': 'Mozilla/5.0','Referer':'https://item.jd.com/11990777.html'}

try:
r = requests.get(url,headers = kv)
r.raise_for_status()
except:
print("spider failed.")
r_json_str = r.text[26:-2]
r_json_obj = json.loads(r_json_str)
pprint.pprint(r_json_obj['comments'])
r_json_comments = r_json_obj['comments']
for r_json_comment in r_json_comments:
with open(kybook_path,'a+') as f:
f.write(r_json_comment['content']+'\n')
print(r_json_comment['content'])


def bunch_comment_spider():
if os._exists(kybook_path):
os.remove(kybook_path)
for i in range(20):
comment_spider(i)
time.sleep(random.random() * 5)

def cut_word():
# 打开爬取的评论数据文件
with open(kybook_path) as file:
# 读取文件内容
comment_txt = file.read()
#分词处理
wordlist = jieba.cut(comment_txt, cut_all=True)
#json处理
wl = " ".join(wordlist)
print(wl)
return wl

def create_word_cloud():
# 设置词云形状图片
wc_mask = np.array(Image.open(WC_MASK_IMG))
# 设置词云的一些配置,如:字体,背景色,词云形状,大小
wc = WordCloud(background_color="white", max_words=2000, mask=wc_mask, scale=4,
max_font_size=50, random_state=42, font_path=WC_FONT_PATH)
# 生成词云
wc.generate(cut_word())
# 在只设置mask的情况下,你将会得到一个拥有图片形状的词云
plt.imshow(wc, interpolation="bilinear")
plt.axis("off")
plt.figure()
plt.show()

if __name__ == '__main__':
#comment_spider()
#bunch_comment_spider()

create_word_cloud()

生成的效果:

image-20190805233245447

我们就可以看到评论里最多的词语,进而可以有一个对这本书大致的分析判断。

CATALOG
  1. 1. 爬取京东《刻意练习》图书评论并生成图词
    1. 1.0.0.1. 实现的目的
    2. 1.0.0.2. 上手
      1. 1.0.0.2.1. 首先,我们需要打开京东关于这本书的评论,这很简单,用浏览器我们可以办得到(推荐使用 Chrome 浏览器,可能会更加方便)
    3. 1.0.0.3. 第一行代码
      1. 1.0.0.3.1. 导入 Requests 库
      2. 1.0.0.3.2. 定义评论链接
    4. 1.0.0.4. 找到评论链接
    5. 1.0.0.5. 打开浏览器开发模式
    6. 1.0.0.6. 真正的开始
    7. 1.0.0.7. 爬取第一页评论
    8. 1.0.0.8. 值得一提的小细节
    9. 1.0.0.9. 数据分析与提取
    10. 1.0.0.10. 翻页处理,打印多页评论
    11. 1.0.0.11. 生成云词