最近在重构我的准备 智慧校园助手2.0
,奈何之前的相关数据放在服务器被小黑子黑了,准备重新搞点数据,借此和大家分享一波我之前做项目没数据该咋搞 👀。
Java面试指南 & 大厂学习导航:www.java2top.cn
完整源码获取:v 搜 [小龙coding
] 回复 jd_spider
事情是这样的:
项目中有一个关于电商的模块,奈何没有数据,然后打算爬取京东,淘宝pc端数据,但是有些图片放在手机端尺寸不符合,综合考虑,决定爬取京东手机端网页数据
相关链接:
我的项目《基于人工智能的智慧校园助手v1.0.0》部分功能展示
www.bilibili.com/video/BV1XT…
《京东商城》
so.m.jd.com/webportal/c…
智慧助手2.0
做了全新架构迭代与功能升级,后面开源给大家,一个面试求职学习的好项目~
我们先看看最后部分数据截图吧,对啦,爬取的数据全部保存为了json
数据,方便各处使用。
最后得到了几十万条数据,然后全部导入了 ElasticSearch
构建了强大的搜索系统与推荐中心。
在此期间,遇到很多问题,也学到很多新知识。我认为,不是你主要从事Java就只会Java相关,语言只是工具,是为了方便我们自身,所以具体应用场景使用什么方便就怎么来吧。当然,Java也可以爬虫,但是我个人感觉没有python强大与方便。
本文记录了我整个过程的所思所想,从开始到结束所有想法思路的产生与遇到的各种问题,然后是怎样去解决问题的以及自己的一些看法与总结,希望给读者带来新的理解与感悟(无论是从技术还是解决问题的思路)。
补充一句:小龙 python 也不太会,若有不足,欢迎大家留言指正,也可以技术交流大家一起学习讨论进步。
废话不多说,跟着小龙的思路,一步一步来看看吧!!
数据库设计
分类信息表(product_category)
CREATE TABLE product_category(
category_id SMALLINT UNSIGNED AUTO_INCREMENT NOT NULL COMMENT '分类ID',
category_name VARCHAR(10) NOT NULL COMMENT '分类名称',
img VARCHAR(100) NOT NULL COMMENT '分类图片logo',
parent_id SMALLINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '父分类ID--若为0则该层为父类',
category_level TINYINT NOT NULL DEFAULT 1 COMMENT '分类层级--该层为该分类第几层',
category_status TINYINT NOT NULL DEFAULT 1 COMMENT '分类状态--是否还可继续往下分,是1否0',
PRIMARY KEY pk_categoryid(category_id)
)ENGINE=innodb COMMENT '商品分类表'
商品信息表(product_info)
CREATE TABLE product_info(
sku_id INT UNSIGNED AUTO_INCREMENT NOT NULL COMMENT '商品ID',
product_name VARCHAR(20) NOT NULL COMMENT '商品名称',
category_id1 SMALLINT UNSIGNED NOT NULL COMMENT '一级分类ID',
category_id2 SMALLINT UNSIGNED NOT NULL COMMENT '二级分类ID',
category_id3 SMALLINT UNSIGNED NOT NULL COMMENT '三级分类ID',
price FLOAT NOT NULL COMMENT '商品销售价格',
publish_status TINYINT NOT NULL DEFAULT 0 COMMENT '上下架状态:0下架1上架',
descript VARCHAR(100) NOT NULL COMMENT '商品描述',
spec_param VARCHAR(10000) NOT NULL COMMENT '商品规格参数',
title VARCHAR(100) NOT NULL COMMENT '商品标题',
PRIMARY KEY pk_productid(product_id)
) ENGINE = innodb COMMENT '商品信息表';
商品图片表(product_pic_info)
CREATE TABLE product_pic_info(
product_pic_id INT UNSIGNED AUTO_INCREMENT NOT NULL COMMENT '商品图片ID',
product_id INT UNSIGNED NOT NULL COMMENT '商品ID',
pic_desc VARCHAR(50) COMMENT '图片描述',
pic_url VARCHAR(200) NOT NULL COMMENT '图片URL',
is_master TINYINT NOT NULL DEFAULT 0 COMMENT '是否主图:0.非主图1.主图',
pic_status TINYINT NOT NULL DEFAULT 1 COMMENT '图片是否有效:0无效 1有效',
PRIMARY KEY pk_picid(product_pic_id)
)ENGINE=innodb COMMENT '商品图片信息表';
分析
3.1、页面分析
每个大类下又分多个2级与3级分类
3.2、页面元素审查
解决:
1.实现思路
第一层获取 category1
(手机数码,家用电器…),第二层爬取 categeoy2
(热门分类,手机通讯…),第三层爬取 categeoy3
(小米,华为,荣耀…)
2.数据请求
网页用 Ajaxs 请求数据每选择一次 category1,下层数据重新加载一次,所以选取 selenium
模拟浏览器自动爬取。
# 总枢纽,控制每一级分类,详情爬取
def spider(driver):
# 得到ul列表
# 显示等待
wait(driver, '//*[@id="category2"]//li')
html = etree.HTML(driver.page_source)
lis = html.xpath('//*[@id="category2"]//li')
# 解析每个li标签
# or
for i, li in enumerate(lis):
# 分类太多,爬取时间太长,选取有用类爬取
if ( i == 1 or i == 3 or i == 8 or i == 26):
# 保存一级分类
category_id1 = li.xpath('./@id')[0]
category_name1 = li.xpath('.//text()')[0]
category_level1 = 1
category_status1 = 1
parent_id1 = 0
# path = 'category%s' %str(i+6)
path = '//*[@id="category%s"]/a' % str(i+6)
# driver.find_element_by_xpath('//*[@id="category18"]/a').click()
# time.sleep(2)
# driver.find_element_by_xpath('//*[@id="category27"]/a').click()
# time.sleep(2)
item = driver.find_element_by_xpath(path)
item.click()
time.sleep(3)
phone_html = etree.HTML(driver.page_source)
branchList = phone_html.xpath('//*[@id="branchList"]/div')
for j, item in enumerate(branchList):
# 一个item就是一个div
# 获取二级分类
print(j)
if(i == 14 and j == 0):
continue
category_name2 = (item.xpath('./h4/text()'))[0]
category_id2 = 'SecCategory'+str(category_id1).split('y')[1]
category_level2 = 2
category_status2 = 1
parent_id2 = category_id1
lis = item.xpath('./ul/li')
for k, li in enumerate(lis):
#这是由于数据太多,我控制category3只要6个就够了
if(k>5):
break
category_id3 = (li.xpath('./a/@id'))[0]
img = li.xpath('./a/img/@src')[0]
href = li.xpath('./a/@href')[0]
# 解析详情页
# ****detail(page_source)****
category_name3 = (li.xpath('.//span/text()'))[0]
category_level3 = 3
category_status3 = 0
parent_id3 = category_id2
print("进入第%s页爬取......" % (k+1))
detail_href = li.xpath('./a/@href')[0]
# 打开商品详情页,另开窗口
driver.execute_script("window.open ('%s')" % detail_href)
driver.switch_to.window(driver.window_handles[1])
time.sleep(2)
flag = isElementExist(driver, '//*[@id="pcprompt-viewpc"]')
if flag:
start = driver.find_element_by_xpath('//*[@id="pcprompt-viewpc"]')
start.click()
print('开始爬取第三级分类商品.....')
# driver.execute_script("arguments[0].click();", li)
# start = time.clock()
# 爬取详情信息
getList(driver, category_id1, category_id2, category_id3)
# end = time.clock()
print("爬取第三级分类商品完成.....")
driver.close()
driver.switch_to.window(driver.window_handles[0])
# 解析详情页
# time.sleep(2)
# 三级
category3 = {
"category_id": category_id3,
"category_name": category_name3,
"category_level": category_level3,
'category_status': category_status3,
"parent_id": parent_id3,
'img': img,
'href': href
}
list.append(category3)
# 二级
category2 = {
"category_id": category_id2,
"category_name": category_name2,
"category_level": category_level2,
'category_status': category_status2,
"parent_id": parent_id2
}
list.append(category2)
# 一级
category1 = {
"category_id": category_id1,
"category_name": category_name1,
"category_level": category_level1,
'category_status': category_status1,
"parent_id": parent_id1
}
list.append(category1)
with open('categoryList.json', 'w', encoding='utf-8') as fp:
json.dump(list, fp, ensure_ascii=False)
3.弹窗问题
由于我开始进入该网站,它跳一个弹窗(类似我下面图)出来覆盖页面,无法用 selenium 进行页面元素获取并点击,然后又有些时候又没得,郁闷,于是,我加了一个函数判断它究竟有没得
# 判断某元素是否存在
def isElementExist(driver, path):
flag = True
try:
driver.find_element_by_xpath(path)
return flag
except:
flag = False
return flag
4.获取 categoryList
分析页面。找到需要的相关信息、
category[
category3 = {
“category_id”: category_id3,
“category_name”: category_name3,
“category_level”: category_level3,
‘category_status’: category_status3,
“parent_id”: parent_id3,
‘img’: img,
‘href’: href
}
]
经过分析,发现三级分类商品a标签超链接即是进去商品列表的入口,通过 href 进入 goods_list。
for k, li in enumerate(lis):
if(k>5):
break
category_id3 = (li.xpath('./a/@id'))[0]
img = li.xpath('./a/img/@src')[0]
href = li.xpath('./a/@href')[0]
# 解析详情页
# ****detail(page_source)****
category_name3 = (li.xpath('.//span/text()'))[0]
category_level3 = 3
category_status3 = 0
parent_id3 = category_id2
print("进入第%s页爬取......" % (k+1))
detail_href = li.xpath('./a/@href')[0]
driver.execute_script("window.open ('%s')" % detail_href)
driver.switch_to.window(driver.window_handles[1])
time.sleep(2)
flag = isElementExist(driver, '//*[@id="pcprompt-viewpc"]')
if flag:
start = driver.find_element_by_xpath('//*[@id="pcprompt-viewpc"]')
start.click()
print('开始爬取第三级分类商品.....')
# driver.execute_script("arguments[0].click();", li)
# start = time.clock()
getList(driver, category_id1, category_id2, category_id3)
# end = time.clock()
print("爬取第三级分类商品完成.....")
5.进入category4列表
分析页面,找到相关信息 我们需要他的
{
skuid
price
title
url
img 图片到详情页与海报图一起获取
}
经分析发现用他的超链接–tourl要重定向到pc端页面
于是又经分析每个商品进入的地址
1)item.m.jd.com/product/100…
2)item.m.jd.com/product/100…
。。。。。。。
发现每个链接就只有他的 skuid
,和 price
不一样,可以一试,但是偶尔又有其他小变化,我觉得很麻烦,于是,经研究js与链接 提取出一个
通用地址item.m.jd.com/product/(skuid).html?m2wq_ismiao=1。那么只要我得到他的skuid就可以顺利进入他的详情页。
收集 sku
部分数据code:
print('开始爬取商品详情页.....')
loadPage(driver, 1)
html = etree.HTML(driver.page_source)
wait(driver, "//*[@id='itemList']/div[@class='search_prolist_item']//div[@class='search_prolist_price']//span")
divs = html.xpath("//*[@id='itemList']/div[@class='search_prolist_item']")
skus = []
for i, div in enumerate(divs):
try:
skuid = div.xpath('./@skuid')[0]
except:
skuid = 0
try:
title = div.xpath(".//div[@class='search_prolist_title']/text()")[0].strip()
except:
title = ""
try:
price = div.xpath(".//div[@class='search_prolist_price']//span/text()")[0].strip()
except:
price = 0
# 这是当时写了个判断,但是出错,又嫌代码缩进不好处理,就改了个1
if (1):
sku = {
'skuid': skuid,
'title': title,
'price': price,
"category_id1": category_id1,
'category_id2': category_id2,
'category_id3': category_id3,
'url': "https://item.m.jd.com/product/%s.html?m2wq_ismiao=1" % skuid,
}
skus.append(sku)
sku = {
‘skuid’: skuid,
‘title’: title,
‘price’: price,
“category_id1”: category_id1,
‘category_id2’: category_id2,
‘category_id3’: category_id3,
‘url’: “https://item.m.jd.com/product/%s.html?m2wq_ismiao=1” % skuid,
}
3.3、详情页解析
获取商品轮播图,商品海报图,商品铺信息,商品介绍,规格参数
1、解析图片(商品轮播图,海报图)
图片的id由于它没有提供,而且简单重复,于是,我在以他图片名字结尾作为id,那么现在自己封装函数解析img_id。
解析img_id code:
# 根据图片地址截取图片名称划取img的id(主键)
def splitImgId(src):
img_id = ''
list = src.split('/')
for i, li in enumerate(list):
if(li.find('.jpg', 0, len(li)) !=-1 or li.find('.png', 0, len(li)) != -1):
for key in li:
if(key == '.'):
break
img_id = img_id + key
return img_id
获取商品海报图–商品介绍:
注:这个太挫了,找了多久也没见它图片在哪里渲染上去的,后来还是终于被我发现他是在style上拿的url
那么接下来自己封装个函数得到他的 background-url:
# 获取海报图imgList
def HB(html, driver):
imgs = []
img = ''
wait(driver, '//*[@id="detail"]')
style = str(html.xpath('//*[@id="commDesc"]/style/text()'))
style = style.split('background-image:url(')
for item in style:
for i, key in enumerate(item):
if (item[0] == '/' and item[1] == '/'):
if (item[i] == ')'):
if (img != ''):
imgs.append(img)
img = ''
break
img = img + item[i]
return imgs
解析图片 code:
# 1.解析图片(包括商品的轮播图,海报图)
def detail_img(html, goods_id, driver):
# 解析商品轮播图
imgs = []
flag = wait(driver, '//*[@id="loopImgUl"]/li')
if(flag == 1):
return imgs
lis = html.xpath('//*[@id="loopImgUl"]/li')
product_id = goods_id
is_master = 0
for i, li in enumerate(lis):
pic_url = li.xpath('./img/@src')[0]
product_pic_id = splitImgId(pic_url)
pic_desc = '商品轮播图'
pic_status = 1
# 主图,爬取的第一个为
if (i == 0):
is_master = 1
img = {
'product_id': product_id,
'is_master': is_master,
'pic_url': pic_url,
'pic_status': pic_status,
'product_pic_id': product_pic_id
}
imgs.append(img)
# 解析商品海报图
for item in HB(html, driver):
pic_url = item
product_pic_id = splitImgId(item)
pic_status = 1
pic_desc = "商品海报图"
img = {
'product_id': product_id,
'is_master': is_master,
'pic_url': pic_url,
'pic_status': pic_status,
'product_pic_id': product_pic_id
}
imgs.append(img)
return imgs
2、获取商铺信息
这个没有啥说头,简单,唯一就是在实践中发现有一些没得店铺得,会导致程序出错,于是我加了一个显示等待,等他20s,没找到那个商铺得元素则说明可能不是网速原因,是没有这个店铺
shop = {}
# 显示等待---封装函数
flag = wait(driver, '//*[@id="shopBaseInfo"]/div[2]/p[1]')
if(flag == 1):
return shop
try:
img = html.xpath('//*[@id="shopLogoInfo"]/span/img/@src')[0]
shop_name = html.xpath('//*[@id="shopInfo"]/p/span/text()')[0]
fan = html.xpath('//*[@id="shopBaseInfo"]/div[1]/p[1]/text()')[0]
goods_num = html.xpath('//*[@id="shopBaseInfo"]/div[2]/p[1]/text()')[0]
except:
return {}
shop = {
'img': img,
'shop_name': shop_name,
3、获取规格参数
这个也没说头,就是把他数据保存就是,一般不遇到错,不过可以加一个捕获异常
# 有问题,就返回空字符串,程序继续执行
try:
specification = {'specification': spec_param(driver)}
except:
specification = {'specification': ''}
try:
sub_title = {'sub_title': html.xpath('//*[@id="itemName"]/text()')[0]}
except:
sub_title = {'sub_title': ''}
剩下就是把解析得到得数据进行整合处理,保存,这就不过多介绍了。
部分成果展示
1.我 category1 一级分类只选了 5 个大类,每个3级分类只抓了14个商品用了7个多小时,深深体会了这痛苦,开始只想快点抓到数据,没考虑代码写的好规范,结果出现很多漏洞,额外反而花了很多时间,接下来项目做完决定优化一下代码,考虑用多线程爬取估计最多1.5 个小时就可以爬完。
2.虽然这段代码我从分析,写,改bug,运行,又错又改花了3天吃了很多苦头,但是最终还是成功了。学到很多,有哪里可以更好解决,可以私聊一起探讨。
如果你对Python感兴趣,想要学习python,这里给大家分享一份Python全套学习资料,都是我自己学习时整理的,希望可以帮到你,一起加油!
😝有需要的小伙伴,可以V扫描下方二维码免费领取🆓
1️⃣零基础入门
① 学习路线
对于从来没有接触过Python的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。
② 路线对应学习视频
还有很多适合0基础入门的学习视频,有了这些视频,轻轻松松上手Python~
③练习题
每节视频课后,都有对应的练习题哦,可以检验学习成果哈哈!
2️⃣国内外Python书籍、文档
① 文档和书籍资料
3️⃣Python工具包+项目源码合集
①Python工具包
学习Python常用的开发软件都在这里了!每个都有详细的安装教程,保证你可以安装成功哦!
②Python实战案例
光学理论是没用的,要学会跟着一起敲代码,动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。100+实战案例源码等你来拿!
③Python小游戏源码
如果觉得上面的实战案例有点枯燥,可以试试自己用Python编写小游戏,让你的学习过程中增添一点趣味!
4️⃣Python面试题
我们学会了Python之后,有了技能就可以出去找工作啦!下面这些面试题是都来自阿里、腾讯、字节等一线互联网大厂,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
上述所有资料 ⚡️ ,朋友们如果有需要的,可以扫描下方👇👇👇二维码免费领取🆓