通过观察京东商品页面返回的评论数据是 JSON 格式的,所以抓取指定评论需要使用 JSON 模块中相应的 API 进行分析,而从搜索页面抓取的商品列表需要分析 HTML 代码,所以使用 bs4。在对数据进行分析整理后,需要将数据保存到 sqlite 数据库中,其他模块还包括 os 和 fake_useragent(获取假的消息头,之前用一个消息头好像被封了,带秀)。
from urllib3 import *
import sqlite3
import json
import os
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
首先我们来到京东随便搜索一个商品,并且进入商品具体页面,检测network
不难发现这个页面获取了评论有关的json数据:
将此url在浏览器中访问后
这并不是纯粹的 json 数据,前面还有 fetchJSON_comment...,以及其他一些不属于 JSON 数据的内容,通过不同的商品面叶可以发现 callback 参数的值基本上都不同,回调函数,不妨去掉试试
哈!果然能正常访问,而且内容中也没有了fetchJSON_啥啥啥的,这又方便了不少
于是我们得到了一个基本格式https://sclub.jd.com/comment/productPageComments.action?productId=***&score=0&sortType=5&page=***&pageSize=10&isShadowSku=0&fold=1,不过要注意的是第一页是0
当然其中还有一些零碎的地方需要转为json类型格式(需要将null,false,true用双引号括起来),可以直接通过字符串替换的方式,然后将json数据转换为字典对象
def getRateDetail(productId, page):
url = 'https://sclub.jd.com/comment/productPageComments.action?productId='+ str(productId) + '&score=0&sortType=5&page='+str(page)+'&pageSize=10&isShadowSku=0&fold=1'
r = http.request('GET', url, headers=head)
c = r.data.decode('GB18030')
c = c.replace('null', '"null"')
c = c.replace('false', '"false"')
c = c.replace('true', '"true"')
# 将json数据转换为字典对象
jdjson=json.loads(c)
return jdjson
并且在此页上有页数信息
以下为获取每个商品页最大评论页数
#获取具体商品的最后一页
def getLastPage(productId):
jdjson = getRateDetail(productId,1)
return jdjson['maxPage']
接下来的任务就是抓取搜索页面的所有商品id,商品页网址如https://item.jd.com/5734174.html,不难猜测5734174就是商品id,来到搜素页
可见每个li标签中的data-sku就是商品id,接下来自然就是通过美汤来获取所有商品id的列表
# 以列表形式返回商品ID
def getProductIdList():
url='https://search.jd.com/Search?keyword=cherry&enc=utf-8&wq=cherry&pvid=2db50b439b0747408233915adca72e88'
r = http.request('GET', url, headers=head)
#注意如果搜索的内容有中文,网址中就会有utf8的东西,需要ignore忽略utf8一下
c = r.data.decode('GB18030','ignore').encode('utf8')
soup = BeautifulSoup(c, 'html.parser')
liList=soup.find_all('li',attrs={'class':'gl-item'})
idList = []
for li in liList:
idList.append(li['data-sku'])
#去掉重复的id,不过应该不会重复(利用Set的特性)
idList = list(set(idList))
return idList
最后便是加入到sqlite数据库(注意执行完要commit一下)当中了
完整代码:
from urllib3 import *
import sqlite3
import json
import os
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
disable_warnings()
http = PoolManager()
head = {
'User-Agent': str(UserAgent().random)
}
dbPath = 'jdkb.sqlite'
if os.path.exists(dbPath):
os.remove(dbPath)
conn = sqlite3.connect(dbPath)
cursor = conn.cursor()
cursor.execute('''create table kb_comment
(id integer primary key autoincrement not null,
content text not null,
ctime text not null,
productName text not null);''')
conn.commit()
#获取具体商品的json数据
def getRateDetail(productId, page):
url = 'https://sclub.jd.com/comment/productPageComments.action?productId='+ str(productId) + '&score=0&sortType=5&page='+str(page)+'&pageSize=10&isShadowSku=0&fold=1'
r = http.request('GET', url, headers=head)
c = r.data.decode('GB18030')
c = c.replace('null', '"null"')
c = c.replace('false', '"false"')
c = c.replace('true', '"true"')
jdjson=json.loads(c)
return jdjson
#获取具体商品的最后一页
def getLastPage(productId):
jdjson = getRateDetail(productId,1)
return jdjson['maxPage']
# 以列表形式返回商品ID
def getProductIdList():
url='https://search.jd.com/Search?keyword=cherry&enc=utf-8&wq=cherry&pvid=2db50b439b0747408233915adca72e88'
r = http.request('GET', url, headers=head)
c = r.data.decode('GB18030','ignore').encode('utf8')
soup = BeautifulSoup(c, 'html.parser')
liList=soup.find_all('li',attrs={'class':'gl-item'})
idList = []
for li in liList:
idList.append(li['data-sku'])
#去掉重复的Utl
idList = list(set(idList))
return idList
init = 0
productIdList=getProductIdList()
while init < len(productIdList):
try:
productId = productIdList[init]
maxPage = getLastPage(productId)
page = 0
while page <= maxPage:
try:
jdjs=getRateDetail(productId,page)
rateList = jdjs['comments']
n=0
while n<len(rateList):
content = str(rateList[n]['content'])
time = str(rateList[n]['creationTime'])
productName = str(rateList[n]['referenceName'])
cursor.execute('''insert into kb_comment(content,ctime,productName)
values('%s','%s','%s')'''%(content,time,productName))
conn.commit()
n += 1
page += 1
except Exception as e:
continue
init+=1
except Exception as e:
print(e)
效果如图
···
由于sqlite一次只能 insert 500条记录,要是想爬大量数据要么分多次爬,要么就使用多线程爬虫。