【零基础学爬虫】爬虫实战:爬取京东零食

简介

使用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.三个导航栏

    显然上面两类商品的详情页面
    不论是在五个导航栏还是三个,我们都需要点击“商品评价”按钮来获取上述后四个数据。但是两类页面明显存在差别,如果页面种类很多,可以考虑换种实现方式,如果页面种类不多,可以采用分类的方式。在本次实现中由于只存在两种页面,我们采用的是分类的方式。
  • 有些数据不会加载出来,必须下拉页面后那部分数据才会加载出来,这是下拉页面就很重要,否则会出现找到不到元素的异常
  • 为了加快网页的访问速度,可以进行必要的浏览器配置:比如,访问时不加载图片,和使用无界面浏览器等

源码和数据:

下面是爬取的数据:


源码
下面是本文的核心代码,扫描下方二维码,发送关键词“京东”即可获取本文的完整源码和详细程序注释
扫码关注,及时获取更多精彩内容。(博主今日头条大数据工程师)
公众号专注:互联网求职面经javapython爬虫大数据等技术、海量资料分享:公众号后台回复“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])

扫描下方二维码,及时获取更多互联网求职面经javapython爬虫大数据等技术,和海量资料分享:公众号后台回复“csdn”即可免费领取【csdn】和【百度文库】下载服务;公众号后台回复“资料”:即可领取5T精品学习资料java面试考点java面经总结,以及几十个java、大数据项目资料很全,你想找的几乎都有
扫码关注,及时获取更多精彩内容。(博主今日头条大数据工程师)

猜你喜欢

转载自blog.csdn.net/liewen_/article/details/89378444