简介
使用Selenium+chrome/PhantomJS爬取京东零食。
京东的页面比较复杂:含有各种请求参数、加密参数,如果直接请求或者分享Ajax的话会非常的繁琐,Selenium是一个自动化测试工具,可以驱动浏览器完成各种操作:模拟点击、输入、下滑等各种功能,如此一来,我们只需要关心操作,而不需要关心后台发生了什么样的请求。PhantomJS是无界面的浏览器,比Selenium方便,phantomJS已经被废弃了,直接配置Selenium的浏览器options就可以实现无界面浏览器(详情请看文末的实现代码)。
本次实战爬取的是京东搜索“零食”出现的所有内容,解析库是PyQuery。
###目标站点分析
分析发现京东搜索“零食”后发起了很多请求,比较复杂。这里使用Selenium驱动浏览器,这可以屏蔽这些复杂请求的分析,所以需要依次实现:
- 模拟京东搜索关键词“美食”
- 模拟鼠标点击
- 获取首页的内容
- 模拟点击翻页或者通过模拟输入翻页
- 网页解析
Selenium和webdriver安装可看下之前的文章
(1)实现步骤
浏览器右键->检查,上图中的copy选项卡可以在元素查找时使用,这篇文章中我们使用css选择器查找元素,点击“copy selector”,在查找元素时直接粘贴就可以了。
在正式写代码前需要考虑到:
- 我们必须要等待输入框和搜索按钮加载完,才能进行输入和搜索操作,这就意味着我们在实现代码中需要加一些等待条件,这个可以上Selenium官网上查看:主要是wait_until函数。
- 加载完后需要获取总页数等一些信息
- 页面切换:可以点击下一页;也可以通过输入框输入页码
- 页面切换之后,通过高亮的页码是否加载完成来判断翻页是否完成
- 完成等待页面加载完成和翻页等之类框架的功能后,我们再考虑细节,即网页内容解析
(2)网页解析
上图可以和清楚的发现,所有页面商品都是以li的形式组织的,页面解析思路也就很明确了。
应该注意的是,在进行网页源码解析之前,我们需要先判断网页是否加载完成了。确认网页加载完后,我们获取网页的源代码,使用PyQuery进行解析(也可以使用正则表达式,上一次实战使用的是正则,这次就用PyQuery解析)。
(3)在真实贴代码和数据之前,先说明几个曾经踩过的坑:
- 确定要爬取的数据:确定目标
- 写代码前 一定要分析完网页源码
- 注意细节:有些网页数据要等待一定时间后才会加载出来,网页源码中包括很多数据,其中大部分数据是通过js请求获取后才会填充到网页源码中,所以有些数据加载的比较慢,所以获取数据前一定要等待数据加载完
- 本次爬取的是京东搜索“零食”关键词后按照评论数量进行排序的数据:商品名字、价格、总评论数、好评数、中评数、差评数、好评率等数据,分析后发现:后面四个数据,必须点击商品详情后才可以获取到,但是不同商品的详情页面有些许不同,下面是两种典型的商品详情页面:
1.五个导航栏
2.三个导航栏
显然上面两类商品的详情页面
不论是在五个导航栏还是三个,我们都需要点击“商品评价”按钮来获取上述后四个数据。但是两类页面明显存在差别,如果页面种类很多,可以考虑换种实现方式,如果页面种类不多,可以采用分类的方式。在本次实现中由于只存在两种页面,我们采用的是分类的方式。 - 有些数据不会加载出来,必须下拉页面后那部分数据才会加载出来,这是下拉页面就很重要,否则会出现找到不到元素的异常
- 为了加快网页的访问速度,可以进行必要的浏览器配置:比如,访问时不加载图片,和使用无界面浏览器等
源码和数据:
下面是爬取的数据:
源码
下面是本文的核心代码,扫描下方二维码,发送关键词“京东”即可获取本文的完整源码和详细程序注释
公众号专注:互联网求职面经、java、python、爬虫、大数据等技术、海量资料分享:公众号后台回复“csdn文库下载”即可免费领取【csdn】和【百度文库】下载服务;公众号后台回复“资料”:即可领取5T精品学习资料、java面试考点和java面经总结,以及几十个java、大数据项目,资料很全,你想找的几乎都有
完整源码和代码详细解析
# 京东首页搜素框
def search(key_words='零食'):
try:
input_text = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#key')))
button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#search > div > div.form > button')))
input_text.send_keys(key_words)
button.click()
total = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#J_topPage > span > i')))
total = int(total.text)
order = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#J_filter > div.f-line.top > div.f-sort > a:nth-child(3)')))
order.click()
except TimeoutException:
print('search 超时')
print('一共', total, '页')
return total
# 翻页
def next_page(next_page):
# 翻页使用的策略是手动输入页码,然后点击跳转,但是有个问题就是:
# 不滑动滑块直接定位页码输入框会出现:找不到该元素的异常,这主要是,页面未加载造成的
# 也就是:当你在京东上搜索商品的时候,浏览器没有下拉到翻页那里时,页面就不会加载,页面没有加载自然找不到对应的元素
# 这也是下面这三条指令的意义所在
# browser.execute_script(js)
try:
browser.execute_script('arguments[0].scrollIntoView()',
browser.find_element_by_css_selector('#footer-2017 > div > div.copyright_info > p:nth-child(1) > a:nth-child(1)'))
time.sleep(2)
# 下滑直到“下一页”按钮出现
browser.execute_script('arguments[0].scrollIntoView()',
browser.find_element_by_css_selector('#J_bottomPage > span.p-num > a.pn-next'))
input_text = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#J_bottomPage > span.p-skip > input')))
jump = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#J_bottomPage > span.p-skip > a')))
except TimeoutException:
print('翻页超时')
input_text.clear()
input_text.send_keys(next_page)
jump.click()
try:
# 等待翻页完成
wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '#J_bottomPage > span.p-num > a.curr'), str(next_page)))
browser.execute_script('arguments[0].scrollIntoView()',
browser.find_element_by_css_selector('#J_bottomPage > span.p-num > a.pn-next'))
except TimeoutException:
print('翻页失败', next_page)
print('\n')
print('解析第', next_page, '页商品')
try:
for good in parse_page():
write_to_csv_file(good.values())
except TimeoutException:
print('第', next_page, '页的第', count_item, '个商品解析时发生超时')
browser.switch_to.window(browser.window_handles[0])
return None
# 页面数据解析
def parse_page():
global count_item
count_item = 0# 统计每一页的商品
# 解析页面前,先将浏览器页面下滑置“下一页”地方
browser.execute_script('arguments[0].scrollIntoView()',
browser.find_element_by_css_selector('#J_bottomPage > span.p-num > a.pn-next'))
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.m-list .ml-wrap #J_goodsList .gl-item')))
doc = pq(browser.page_source, parser="html")
goods = doc('.m-list .ml-wrap #J_goodsList .gl-item').items()
browser.switch_to.window(browser.window_handles[1])
for good in goods:
count_item = count_item +1
print('当前是第',count_item,'个商品')
detail_herf = good.find('.p-name a').attr('href')
browser.get('http://'+detail_herf)
# 等待商品介绍 评价等数据加载完成
browser.execute_script('arguments[0].scrollIntoView()',
browser.find_element_by_css_selector('.detail #detail .tab-main ul li'))
detail_doc = pq(browser.page_source,parser='html')
# 京东上主要有两类商品:
# 一类:与商品介绍并列的一共5个标签
# 第二类:只有商品介绍和评价两类标签,
# 下面这个css定位很容易写错
ul = list(detail_doc('.detail #detail .tab-main ul li').items())
if len(ul)==5 :
# 商品评价按钮 五个li标签,对应第二类商品
#等待评价按钮加载完成
comments_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR,'#detail > div.tab-main.large.detail-elevator > ul > li:nth-child(2)')))
comments_button.click() # 点击“评价”按钮
rate = wait.until(EC.presence_of_element_located(
(By.CSS_SELECTOR, '#i-comment > div.rate > strong')))# 等待rate加载完成
rate_comments = rate.text # rate是webElement类型,调用text即可获取对应的文本,注意不是调用text()
# 等待差评数量加载完成(差评数量加载完了也就意味着好评和中评数量加载完了)
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#comments-list > div.mt > div > ul > li:nth-child(4) > a > em')))
tmp_doc = pq(browser.page_source, parser='html')
comments_obtain = tmp_doc('#comments-list .mt-inner ul li').items()# 获取评论数量
index = 0
for comment in comments_obtain:
if index == 1:
good_comments = comment('a em').text()[1:-1]
elif index == 2:
neutral_comments = comment('a em').text()[1:-1]
elif index == 3:
bad_comments = comment('a em').text()[1:-1]
break
index = index + 1
yield {
'name': good.find('.p-name a em').text().strip().replace('\n', ' '),
'price': good.find('.p-price i').text(),
'total_comments': good.find('.p-commit a').text(),
'good_comments': good_comments,
'neutral_comments': neutral_comments,
'bad_comments': bad_comments,
'good_rate': rate_comments
}
elif len(ul)==7 :# 对应第二类商品
comments_button = wait.until(
EC.element_to_be_clickable((By.CSS_SELECTOR, '#detail > div.tab-main.large > ul > li:nth-child(5)')))
comments_button.click()#点击“评价”按钮
rate = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'#comment > div.mc > div.comment-info.J-comment-info > div.comment-percent > div')))
rate_comments = rate.text# rate是webElement类型,调用text即可获取对应的文本,注意不是调用text()
# 等待差评数量加载完成(差评数量加载完了也就意味着好评和中评数量加载完了)
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR,'#comment > div.mc > div.J-comments-list.comments-list.ETab > div.tab-main.small > ul > li:nth-child(7) > a > em')))
tmp_doc = pq(browser.page_source,parser='html')
comments_obtain = tmp_doc('.tab-main .filter-list li' ).items()
index = 0
for comment in comments_obtain:
if index==4:
good_comments = comment('a em').text()[1:-1]
elif index==5:
neutral_comments = comment('a em').text()[1:-1]
elif index==6:
bad_comments = comment('a em').text()[1:-1]
break
index = index + 1
yield {
'name': good.find('.p-name a em').text().strip().replace('\n', ' '),
'price': good.find('.p-price i').text(),
'total_comments': good.find('.p-commit a').text(),
'good_comments':good_comments,
'neutral_comments':neutral_comments,
'bad_comments':bad_comments,
'good_rate': rate_comments
}
browser.switch_to.window(browser.window_handles[0])
扫描下方二维码,及时获取更多互联网求职面经、java、python、爬虫、大数据等技术,和海量资料分享:公众号后台回复“csdn”即可免费领取【csdn】和【百度文库】下载服务;公众号后台回复“资料”:即可领取5T精品学习资料、java面试考点和java面经总结,以及几十个java、大数据项目,资料很全,你想找的几乎都有