zoukankan      html  css  js  c++  java
  • 第七部分(三) 动态渲染页面爬取(用Selenium获取淘宝商品,不涉及验证登录)


    三、 使用 Selenium 爬取淘宝商品

    在分析 Ajax 抓取相关数据时,不是所有页面都可以通过分析 Ajax 来完成抓取。比如淘宝的整个页面数据确实是通过 Ajax 获取的,但这些 Ajax 接口参数复杂,并且包含有加密密钥等,如果要构造 Ajax 参数是很困难。像这种页面最方便的抓取方法是通过 Selenium 。接下就用 Selenium 模拟浏览器操作,抓取淘宝的商品信息,并用 pyquery 解析到商品的图片、名称、价格、购买人数、店铺名称和店铺所的地信息,并将结果保存到 MongoDB。

    接下来需要用到的软件和库有:
    Chrome浏览器软件
    ChromeDriver驱动
    Firefox浏览器并配置相应的驱动 GeckoDriver。
    PhantomJS:一个无界面、可脚本编程的 webkit 浏览器引擎,支持多种 web 标准:DOM 操作, CSS 选择器,JSON、Canvas以及SVG。
    Python 的 Selenium 库。

    1、 接口分析
    先分析淘宝的接口,看下它比一般的 Ajax 多了什么内容。进入淘宝首页,搜索商品,搜索 华为P20,进入开发者工具,未发现淘宝页面的 Ajax 请求。

    2、 页面分析
    这次爬取的是商品信息,包括商品图片、名称、价格、购买人数、店铺名称和店铺所在地等信息。抓取入口是淘宝的搜索页面,这个连接可以通过直接构造参数访问。例如搜索 华为P20,可直接访问 https://s.taobao.com/search?q=华为P20 ,展示的是第一页的搜索结果。在页面下方有分布导航,包括了前5页的连接,也包括下一页的连接,同时还有一个输入任意页面跳转的连接。如图3-1 所示。
    图 4-1 分页导航
    图3-1 分页导航

    可以看出搜索结果最大为 100 页,任意页面输入框的值默认是第2页。要获取每一页的内容,将页码从1 到 100 顺序遍历即可,页码数是确定的。可直接在页面跳转文本框中输入要跳转的页码,点击“确定”可跳转到页码对应的页面。

    这里没有点击下一页来获取,是因为爬取过程中出现异常退出时,此时点击“下一页”就不能切换到对应的后续页面。因此在爬取过程中还要记录当前页码数,如果点击下一页后加载失败,还要做异常检测,检测当前页面是加载到第几页。整个流程相对复杂,所以直接用跳转方式爬取页面。

    成功加载一页商品列表时,利用 Selenium 来获取页面源代码,再利用相应的解析库解析,这里使用 pyqeury 解析。

    3、 获取商品列表
    首先构造要抓取的URL:https://s.taobao.com/search?q=华为P20。参数 q 要是搜索的关键字。只要改变这个参数,就可获取不同商品的列表。可将商品的关键字定义成变量,接着构造出这样一个 URL。然后利用 Selenium 获取源代码,抓取列表页的方法如下:

    from selenium import webdriver
    from selenium.common.exceptions import TimeoutException
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.support.wait import WebDriverWait
    from urllib.parse import quote

    browser = webdriver.Chrome() # 初始化 Chrome浏览器对象
    wait = WebDriverWait(browser, 10) # 设置等待时间10秒
    KEYWORD = '华为P20' # 设置搜索关键字

    def index_page(page):
    """
    抓取索引面
    :param page: 页码
    """
    print('正在爬取第', page, '页')
    try:
    url = 'https://s.taobao.com/search?q=' + quote(KEYWORD) # 构造 URL
    browser.get(url) # 连接 URL,首先访问搜索商品的连接,判断页码是否大于 1,
    if page > 1:
    input = wait.until(
    # 使用 CSS 选择器获取下面的 input标签,也就是页码输入框
    EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-pager div.form > input'))
    )
    submit = wait.until(
    # 使用 CSS 选择器获取到 span标签的“确定”按钮
    EC.element_to_be_clickable((By.CSS_SELECTOR, '#mainsrp-pager div.form > span.btn.J_Submit'))
    )
    input.clear() # 清除输入框的默认值
    input.send_keys(page) # 给输入框输入要获取的页码
    submit.click() # 输入页码后点击提交
    # page等于1或者大于1都执行下面的代码
    #EC.text_to_be_present_in_element((By.CSS_SELECTOR, "#mainsrp-pager li.item.active > span"), str(page))
    obj = EC.text_to_be_present_in_element((By.CSS_SELECTOR, "#mainsrp-pager li.item.active > span"), str(page))
    print(obj)
    wait.until(
    # 使用 CSS 选择器获取当前 span 标签中的页码,并让页面与 page 的页码比较
    EC.text_to_be_present_in_element((By.CSS_SELECTOR, "#mainsrp-page li.item.active > span"), str(page))
    )
            # 等待商品信息加载出来,并使用选择器选择商品信息,调用 get_products() 方法提取商品信息,
    # 如果不能正常加载就报 TimeoutException 异常
    wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.m-itemlist .items .item')))
    get_products()
    except TimeoutException:
    #index_page(page)
    print("请求超时")

    上面这段代码首先使用 Chrome 浏览器构造一个 WebDriver 对象,接着指定一个关键词 华为P20,接着定义 index_page() 方法,用于抓取商品列表页。在 index_page() 方法中先访问搜索商品的链接,然后进行判断当前的页码,如果大于1就进行跳转页操作,否则等待页面加载完成。

    等待加载时,使用了 WebDriverWait 对象,并指定等待条件,同时指定一个最长等待时间,这里是 10 秒。在这个时间内成功匹配等待条件,就说明页面元素成功加载出来,接着返回相应结果并继续向下执行,否则到了最大等待时间还没有加载出来,就直接抛出超时异常。

    要等待商品信息加载出来,可指定 presence_of_element_located 这个条件,然后传入 .m-itemlist .items .item 这个CSS选择器,选择器对应的内容就是每个商品的信息块,可以网页源代码上查看。加载成功就执行后面的 get_products() 方法,提取商品信息。

    对于翻页操作,先获取页码输入框,赋值为 input,接着获取“确定”按钮,赋值为 submit。接着调用 clear() 方法清空输入框,再调用 send_keys() 方法将页码填充到输入框中点击“确定”按钮即可。成功跳转后页码会高亮显示,只要对当前高亮的页码判断是当前的页码数即可,所以使用了另一外等待条件 text_to_be_present_in_element,它会等待指定的文本出现在某一个节点里面时即返回成功。将高亮的页码节点对应的 CSS 选择器和当前要跳转的页码通过参数传递给这个等待条件,这样它就会检测当前高亮的页面节点是不是本次传过来的,如果是就证明页面跳转到了这一页,页面就跳转成功。

    这样在 index_page() 方法中可传入对应的页码,待加载出对应的页码商品列表后,再调用 get_products() 方法进行页面解析。

    4、 解析商品列表
    下面就使用 get_products() 方法对商品列表进行解析。直接获取页面源代码用 pyquery 进行解析。代码如下:

    from pyquery import PyQuery as pq
    def get_products():
    """
    提取商品数据
    """
    html = browser.page_source # 获取网页源代码
    doc = pq(html) # 构造 pyquery 对象
    # 使用 CSS 选择器获取商品信息列表,匹配的是整个页面的每个商品,匹配结果有多个
    items = doc("#mainsrp-itemlist .items .item").items()
    for item in items:
    # 遍历每个商品,每一个 item 变量都是 pyquery 对象,调用它的 find() 方法,使用 CSS 选择器获取单个商品特定内容
    product = {
    'image': item.find('.pic .img').attr('data-src'),
    'price': item.find('.price').text().replace(" ",""),
    'deal': item.find('.deal-cnt').text(), # 购买人数
    'title': item.find('.title').text().strip(), # 商品描述信息
    'shop': item.find('.shop').text(), # 商家信息
    'location': item.find('.location').text() # 商家所在地区
    }
    print(product)
    #save_to_mongo(product) # 将获取到的商品信息保存到 Mongodb

    先来看一下商品信息的源码,如图3-2所示。
    图 4-2 商品信息源码
    图3-2 商品信息源码

    由图3-2 可知,它是一个 img 节点,包含id、class、data-src、alt和 src 等属性。src属性是图片的URL,此外还有 data-src 属性,也是图片的URL,不过它是完整的大图,src 是压缩后的小图,这里获取 data-src 属性来作为商品的图片。因此,先用 find()方法找到图片节点,接着调用 attr() 方法获取商品的 data-src 属性,这样就提取到商品图片连接。用同样的方法提取商品中的价格、成交量、名称、店铺所在地等信息。将提取结果赋值为 product 字典,接着调用 save_to_mongo() 方法将其保存到 MongoDB。

    5、 保存到 MongoDB
    将商品信息保存到 MongoDB的源代码如下:

    import pymongo
    MONGO_URL = 'localhost'
    MONGO_DB = 'taobao'
    MONGO_COLLECTION = 'products_hwP20'
    client = pymongo.MongoClient(MONGO_URL) # 连接 MongoDB
    db = client[MONGO_DB] # 指定数据库
    def save_to_mongo(result):
    """
    保存到 MongoDB
    :param result: 结果
    """
    try:
    # 指定 Collection 名称,并将数据插入到 MongoDB
    if db[MONGO_COLLECTION].insert(result):
    print('存储到 MongoDB 成功')
    except Exception:
    print('存储到 MongoDB 失败')

    6、 遍历每页和运行
    前面定义的 get_index() 方法需要接收参数 page,使用 for 循环对页码遍历,传递 page 参数即可。代码如下所示:

    MAX_PAGE = 100 # 定义最大访问的页面数
    def main():
    """
    遍历每一页
    """
    for i in range(1, MAX_PAGE + 1):
    index_page(i)
    browser.close()

    if __name__ == '__main__':
    main()

    成功运行代码的话,所有的信息都会保存到 MongoDB里面。

    7、 Chrome Headless 模式
    Chrome 最新的版本(从Chrome 59版本开始)支持 Headless 模式,即无界面模式,这样在爬取的时候不会弹出浏览器。使用无界面模式的方式如下:
    chrome_options = webdriver.ChromeOptions()
    chrome_options.add_argument('--headless')
    browser = webdriver.Chrome(chrome_options=chrome_options)

    创建ChromeOptions对象,接着添加 headless 参数,在初始化 Chrome 对象时通过 chrome_options传递这个 ChromeOptions对象,这样就启用 Chrome 的 Headless 模式。

    要使用 Firefox 浏览器,只需要改这一处即可:
    browser = webdriver.Firefox()

    还可以使用 PhantomJS 的无界面浏览器抓取数据,同样不弹出窗口,只需要将 WebDriver 的声明修改如下即可:
    browser = webdriver.PhantomJS()
    PhantomJS 还支持命令行配置,例如,可以设置缓存和禁用图片加载功能来提高爬取效率:
    SERVICE_ARGS = ['--load-images=false', '--disk-cache=true']
    browser = webdriver.PhantomJS(service_args=SERVICE_ARGS)

    最后,完整的代码如下所示:

    import pymongo
    from selenium import webdriver
    from selenium.common.exceptions import TimeoutException
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.support.wait import WebDriverWait
    from urllib.parse import quote
    from pyquery import PyQuery as pq

    chrome_options = webdriver.ChromeOptions()
    chrome_options.add_argument('--headless')
    browser = webdriver.Chrome(chrome_options=chrome_options) # 初始化 Chrome浏览器对象
    #browser = webdriver.Chrome() # 初始化 Chrome浏览器对象
    #browser = webdriver.Firefox() # 初始化 Firefox浏览器对象
    # SERVICE_ARGS = ['--load-images=false', '--disk-cache=true']
    # browser = webdriver.PhantomJS(service_args=SERVICE_ARGS)
    wait = WebDriverWait(browser, 10) # 设置等待时间10秒
    KEYWORD = '华为P20' # 设置搜索关键字

    def index_page(page):
    """
    抓取索引面
    :param page: 页码
    """
    print('正在爬取第', page, '页')
    try:
    url = 'https://s.taobao.com/search?q=' + quote(KEYWORD) + '&imgfile=&js=1&stats_click=search_radio_all%3A1&initiative_id=staobaoz_20190329&ie=utf8' # 构造 URL
    #url = 'https://s.taobao.com/search?q=' + KEYWORD # 构造 URL
    browser.get(url) # 连接 URL,首先访问搜索商品的连接,判断页码是否大于 1,
    if page > 1:
    input = wait.until(
    # 使用 CSS 选择器获取下面的 input标签,也就是页码输入框
    EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-pager div.form > input'))
    )
    submit = wait.until(
    # 使用 CSS 选择器获取到 span标签的“确定”按钮
    EC.element_to_be_clickable((By.CSS_SELECTOR, '#mainsrp-pager div.form > span.btn.J_Submit'))
    )
    input.clear() # 清除输入框的默认值
    input.send_keys(page) # 给输入框输入要获取的页码
    submit.click() # 输入页码后点击提交
    # page等于1或者大于1都执行下面的代码
    #EC.text_to_be_present_in_element((By.CSS_SELECTOR, "#mainsrp-pager li.item.active > span"), str(page))
    #obj = EC.text_to_be_present_in_element((By.CSS_SELECTOR, "#mainsrp-pager li.item.active > span"), str(page))
    #print(obj)
    wait.until(
    # 使用 CSS 选择器获取当前 span 标签中的页码,并让页面与 page 的页码比较
    EC.text_to_be_present_in_element((By.CSS_SELECTOR, "#mainsrp-pager li.item.active > span"), str(page))
    )
    # 等待商品信息加载出来,并使用选择器选择商品信息,调用 get_products() 方法提取商品信息,如果不能正常加载就报 TimeoutException 异常
    wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.m-itemlist .items .item')))
    get_products()
    except TimeoutException:
    #index_page(page)
    print("请求超时")

    def get_products():
    """
    提取商品数据
    """
    html = browser.page_source # 获取网页源代码
    doc = pq(html) # 转为 pyquery 对象
    # 使用 CSS 选择器获取商品信息列表,匹配的是整个页面的每个商品,匹配结果有多个
    items = doc("#mainsrp-itemlist .items .item").items()
    for item in items:
    # 遍历每个商品,每一个 item 变量都是 pyquery 对象,调用它的 find() 方法,使用 CSS 选择器获取单个商品特定内容
    product = {
    'image': item.find('.pic .img').attr('data-src'),
    'price': item.find('.price').text().replace(" ",""),
    'deal': item.find('.deal-cnt').text(), # 购买人数
    'title': item.find('.title').text().strip(), # 商品描述信息
    'shop': item.find('.shop').text(), # 商家信息
    'location': item.find('.location').text() # 商家所在地区
    }
    print(product)
    save_to_mongo(product) # 将获取到的商品信息保存到 Mongodb

    # 将商品信息保存到 MongoDB
    MONGO_URL = 'localhost'
    MONGO_DB = 'taobao'
    MONGO_COLLECTION = 'products_hwP20'
    client = pymongo.MongoClient(MONGO_URL) # 连接 MongoDB
    db = client[MONGO_DB] # 指定数据库
    def save_to_mongo(result):
    """
    保存到 MongoDB
    :param result: 结果
    """
    try:
    if db[MONGO_COLLECTION].insert(result):
    print('存储到 MongoDB 成功')
    except Exception:
    print('存储到 MongoDB 失败')


    MAX_PAGE = 100 # 定义最大访问的页面数
    def main():
    """
    遍历每一页
    """
    for i in range(1, MAX_PAGE + 1):
    index_page(i)
    browser.close()

    if __name__ == '__main__':
    main()
  • 相关阅读:
    Myeclipse安装svn插件
    Hudson+Maven+Svn搭建持续集成环境
    svn merge和branch分析
    Linux下安装subversion1.6.5和apache2
    C语言中,为什么字符串可以赋值给字符指针变量
    Loadrunner C 编程_1
    oracle解决多表关联分组查询问题
    学习英语
    使用 JMeter 完成常用的压力测试 [转]
    Jmeter零起点学习
  • 原文地址:https://www.cnblogs.com/Micro0623/p/10646879.html
Copyright © 2011-2022 走看看