Python+Selenium实战——爬取淘宝商品信息

Selenium在爬虫中的应用

一、利用Selenium爬取动态渲染页面

比如淘宝页面,它即使是Ajax获取的数据,但是其中的Ajax接口含有很多加密参数,我们难以找出其中规律,也很难直接分析Ajax来抓取,为了解决这些问题,我们可以直接使用模拟浏览器运行的方式来实现,这样就可以做到在浏览器中看到是什么样的,抓取的源码就是什么样的【可见即可爬】,这样我们就不用再去管网页内部的javaScript用了什么算法渲染页面,不管网页后台的Ajax接口到底有哪些参数

Selenium 是一个自动化测试工具,利用它可以驱动浏览器执行特定的行为,最终帮助爬虫开发者获取到网页的动态内容。简单的说,只要我们在浏览器窗口中能够看到的内容,都可以使用 Selenium 获取到,对于那些使用了 JavaScript 动态渲染技术的网站,Selenium 会是一个重要的选择。下面,我们还是以 Chrome 浏览器为例,来讲解 Selenium 的用法,大家需要先安装 Chrome 浏览器并下载它的驱动。

二、Selenium的基本使用

Selenium是一个自动化测试工具,利用它可以驱动浏览器执行特定的动作,如点击、下拉等操作、同时还可以获取浏览器当前呈现的页面的源代码,对于JavaScript动态渲染的页面来说,这种抓取方式非常有效

2.1 准备工作

这里我们以谷歌浏览器(Chrome)为例来讲解Selenium的用法,在开始之前,请大家确保已经安装好了Chrome浏览器以及对应的ChromeDriver

step1:下载Chrome浏览器的驱动程序
Chrome 浏览器的驱动程序可以在 ChromeDriver官网 进行下载,先查看自己的谷歌浏览器对应的版本,驱动的版本要跟浏览器的版本对应,在这里插入图片描述
如果没有完全对应的版本,就选择版本代号最为接近的版本,请在测试版本地址选择与你当前浏览器所对用的版本
在这里插入图片描述

step2:正确配置驱动文件所在路径
下载好驱动之后将驱动文件解压,将 chromedriver.exe 文件与你的 py 文件放在同一个目录下,当然也可以通过配置环境变量或者在代码中指定驱动文件的绝对路径在调用驱动(这里不做过多介绍)
在这里插入图片描述

step3:安装对应的第三方库Selenium

# 正常安装
pip install selenium

# 利用镜像快速安装
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple selenium

安装完成后可通过以下的命令查看安装的版本

pip show selenium

2.2 上手演练

Selenium 4.0以后提供了By对象来动态传入条件,等价于之前版本中的find_element_by_查找方式(值)
通过方法find_element(By.查找条件, 条件值)【用之前需要先导入一下By对象】

driver.find_elements(By.查找条件, "条件值")

Selenium4.0以后已经取消了下面这种方式,感兴趣的同学可以自行了解

driver.find_elements_by_id("id 属性值") 
driver.find_elements_by_name("name 属性值") 
driver.find_elements_by_class_name("class 属性值") 
driver.find_elements_by_link_text("链接的文本") 
driver.find_elements_by_partial_link_text("链接的一部分文本") 
driver.find_elements_by_tag_name("HTML 标签名称") 
driver.find_elements_by_xpath("XPath 表达式") 
driver.find_elements_by_css_selector("CSS 选择器")

在这里插入图片描述
下面为百度的文本框对应的 HTML 源码

<input type="text" class="s_ipt" name="wd" id="kw" maxlength="100" autocomplete="off">

案例演示代码

from selenium import webdriver
from selenium.webdriver.common.by import By


driver = webdriver.Chrome()   # 声明浏览器对象
driver.get("https://www.baidu.com")

# 定位文本框
# 方式 1:按照 id 属性查找定位到文本框
searchTextBox = driver.find_element(By.ID, 'kw')

# 方式 2: 按照 name 属性查找定位到文本框
# searchTextBox = driver.find_element(By.NAME, 'wd')

# 方式 3: 按照 class 属性查找定位到文本框
# searchTextBox = driver.find_element(By.CLASS_NAME, 's_ipt')


# 模拟用户输入行为
searchTextBox.send_keys('')

# 通过Xpath获取元素
su_button = driver.find_element(By.XPATH, '//input[@type="submit" and @value="百度一下" and @class="bg s_btn"]')

# 模拟用户点击行为
su_button.click()

三、Selenium的详细介绍

3.1 浏览器窗口的基本操作

包含有以下相关操作,这里不做过多介绍,大家可以根据实际需要去查看相应的博文学习
1.浏览器导航操作
2.浏览器窗口操作
3.获取浏览器信息

3.2 查找页面元素

前文说过 Selenium 4.0以后提供了By对象来动态传入条件,等价于之前版本中的find_element_by_查找方式(值)
通过方法find_element(By.查找条件, 条件值)【用之前需要先导入一下By对象】

  1. id 属性查找
  2. name 属性查找
  3. class 属性查找
  4. 按链接文本查找
  5. 按链接文本进行模糊查找
  6. 按标签类型查找
  7. XPath 查找

1.基于绝对路径或者相对路径定位
2.基于索引或属性定位
3.基于轴定位
4.基于函数或者表达式定位

  1. CSS 选择器查找

1.通过层级关系定位
2.基于关键属性定位
3.基于属性进行模糊定位
4.组合式定位

注意:find_element()find_elements() 方法的区别
find_element()只能获取匹配的第一个结点【结果是WebElement类型】find_elements()返回的是一个列表【列表中的每个结点都是WebElement类型】

3.3 页面元素的基本操作

  1. 单击元素
  2. 向元素输入内容或上传附件
  3. 清空元素的内容
  4. 提交表单元素
  5. 下拉框元素的选项操作

下拉框的操作和之前几种操作方式略有不同,之前介绍的操作都用 WebElement 对象的函数来进行操作,但下拉框使用 Select 对象的函数,因此需要先将 WebElement 对象转换成 Select 对象才能进行下拉框的操作

3.4 获取页面元素的内容

3.4.1 获取元素的基本属性
  1. 获取元素的文本值
  2. 获取元素的标签类型
  3. 获取元素的选中状态
  4. 获取元素的可编辑状态
  5. 判断元素是否已显示
3.4.2 获取元素的 HTML 属性、DOM 属性及 CSS 属性
3.4.3 获取元素的位置与大小
3.4.4 获取下拉框元素的选项

3.5 Selenium的等待机制【重要

3.5.1 元素级等待机制——强制等待

利用time模块中的time.sleep()来实现,这种做法的弊端很明,处理起来毫无弹性。执行的速度和机器的性能有关,如果有一台机器严重卡顿,登录面板有可能 3s 都弹不出来

3.5.2 元素级等待机制——隐式等待
webdriver.implicitly_wait(等待秒数) 

是在执行 find_element()/find_elements()这类函数时,如果在规定的时间内找到元素那么将立即结束等待,否则会一直等待,如果超过时间元素还未出现就会抛出找不到元素的异常
案例演示

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.implicitly_wait(10)	# 设置的最长隐式等待时间为 10s
driver.get("https://www.baidu.com")
driver.find_element(By.LINK_TEXT, "登录").click()
driver.find_element(By.LINK_TEXT, "立即注册").click()

在这个案例中虽然设置的最长隐式等待时间为 10s,但由于通常 1s 内登录面板就会弹出,因此 Selenium 检测到元素已经存在就会结束等待立即执行下一行代码

注:隐式等待是全局设置的,一旦设置会在整个WebDriver实例的生命周期内生效

3.5.3 元素级等待机制——显示等待

元素级等待的最佳实践方式是显示等待,显示等待机制只需要指定条件判断函数,Selenium会每隔一定的时间检测该条件是否成立,如果成立就立刻执行下一步,否则一直等待,直到超过最大等待时间抛出超时异常

from selenium.webdriver.support.wait import WebDriverWait 

# WebDriverWait 的实例化方法如下
WebDriverWait(WebDriver实例, 超时秒数, 检测时间间隔[可选], 可忽略异常集合[可选] ) 
'''
可选参数1:检测时间间隔:调用 until 或 until_not 传入的条件判断函数的间隔时间,默认为 0.5s
可选参数2:可忽略异常集合:在调用 until 或 until_not 中传入的条件判断函数时,如果抛出的是这个集合中定义的异常,代码就不会执行失败,会继续正常执行
'''

实例化 WebDriverWait 对象后,可以调用 WebDriverWait 对象的以下两个函数来执行等待,等待直到条件判断函数的返回值不为 False

  • WebDriverWait.until(条件判断函数, "超时后的自定义异常消息"[可选])
  • WebDriverWait.until_not(条件判断函数, "超时后的自定义异常消息"[可选])

1.使用自定义等待条件函数
在单击"登录"按钮之后,使用 WebDriverWait 对象进行显示等待,超时时间设置为10s,并传入了前面定义的条件判断函数,执行显示等待时,Selenium 会每隔 0.5s 调用一次条件判断函数registerLinkDisplayed,如果条件满足,会立即执行下一行代码,以单击“立即注册”链接

自定义等待条件函数案例

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait


def registerLinkDisplayed(webDriver):
	"""
	自定义等待条件函数
	"""
    return webDriver.find_element(By.LINK_TEXT, "立即注册").is_displayed()


driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
driver.find_element(By.LINK_TEXT, "登录").click()
WebDriverWait(driver, 10).until(registerLinkDisplayed)
driver.find_element(By.LINK_TEXT, "立即注册").click()

2.使用Selenium预定义的等待条件函数
除了自定义条件判断函数之外,还可以使用 Selenium 中已经定义好的一系列条件判断函数,无需再编写类似函数,Selenium 中预定义的等待条件函数位于selenium.webdriver.support.expected_conditions 模块中,需要导入后才能使用,Selenium 中预定义了非常丰富的条件等待函数,同学们可在实际项目需要的使用自行查表,这里不做过多介绍

使用Selenium预定义的等待条件函数案例

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions

driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
driver.find_element(By.LINK_TEXT, "登录").click()
targetLocator = (By.LINK_TEXT, "立即注册")
WebDriverWait(driver, 10).until(expected_conditions.visibility_of_element_located(targetLocator))
driver.find_element(By.LINK_TEXT, "立即注册").click()

该案例使用了expected_conditions中预定义的条件判断函数 visibility_of_element_located,并向该函数中传入了用于判断的目标元素的定位的targetLocator ,在执行显式等待时,Selenium 会判断目标元素是否处于可见状态,条件满足后才执行下一行代码【点击立即注册按钮】

3.6 对键盘和鼠标进行精准模拟

3.6.1 ActionChains——操作链

之前介绍 WebDriver 基础运用时,已经介绍了单击click元素以及在元素中输入内容的 send_keys 函数。它们能涵盖日常使用中的绝大部分场景,但有些时候,我们需要对键盘或鼠标进行更加复杂的模拟与控制,这就需要用到 Selenium 中的高级特性之一ActionChains 了,ActionChains 是一种偏向底层的自动化交互方式,它可以实现鼠标移动、单击、右击、双击、鼠标按下或松开、悬停拖曳、按键按下或松开、按组合键等更复杂的操作

ActionChains基础操作演示

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains


driver = webdriver.Chrome()
# 进入百度首页
driver.get("https://www.baidu.com")

# .click(driver.find_element(By.LINK_TEXT, "登录")这里并不执行单击操作(而是在操作链中预约了一个单击操作,操作对象是"登录按钮")

action = ActionChains(driver).click(driver.find_element(By.LINK_TEXT, "登录"))

# .perform():执行操作链中的所有操作(之前预约的click操作此时才会真正执行)
action.perform()
3.6.2 ActionChains——模拟复杂鼠标操作案例(拖放操作)

通过 ActionChains 提供的操作设置,可以轻松实现元素拖放的功能,这在实际测试和爬虫代码中十分有用

动作链示例演示代码

from time import sleep
from selenium import webdriver
from selenium.webdriver.common.by import By
# 1.导入动作链对应的类
from selenium.webdriver.common.action_chains import ActionChains


driver = webdriver.Chrome()

driver.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')

# 如果定位的标签是存在于iframe标签之中的则必须通过如下操作在进行标签定位
driver.switch_to.frame('iframeResult')  # 切换浏览器标签定位的作用域
div = driver.find_element(By.ID, 'draggable')

# 2.实例化操作链
action = ActionChains(driver)
# 3.点击长按指定的标签动作("预约动作")
action.click_and_hold(div)

for i in range(5):
    # 4.perform()立即执行动作链操作
    # move_by_offset(x,y):x水平方向 y竖直方向
    action.move_by_offset(17, 0).perform()
    sleep(0.5)

# 5.释放动作链
action.release()

driver.quit()

ActionChains 最大的特色不仅仅支持更多复杂的操作,还支持链式操作方式(即每个函数都会返回 ActionChains 实例以便继续调用 ActionChains 的操作设置函数)将多个操作组合在一起形成一个完整的操作链,然后通过 perform 一起执行

3.6.3 ActionChains——模拟复杂键盘操作案例(组合键)

通过 ActionChains 还可以对键盘操作进行精准模拟,这里不做过多介绍

3.7 通过 JavaScript 执行器执行深度操作

虽然 Selenium 支持非常丰富的操作,但还是会遇到极少数无法处理的场景,此时可能需要使用 JavaScripts 执行器来扩展 Selenium,通过 JavaScript 执行器,Selenium 会向浏览器注入 JavaScript 脚本并执行它们,以实现额外的功能。这很像在 Chrome 浏览器中按 F12 键,在弹出的窗口的 Console 标签页中输入JavaScript 脚本,脚本就会在浏览器中执行

webdriver.execute_script("JavaScript 脚本", 自定义参数集(可选)) #执行同步脚本

四、实战案例:使用 Selenium 爬取淘宝商品信息

4.1 结果演示

先看看最后爬取的数据的效果,将数据爬取下来后保存至 excel 文件中,当然你也可以保存到数据库中
在这里插入图片描述

1. 准备工作

我们准备利用 Selenium 抓取淘宝商品并用 pyquery 解析得到商品的图片、名称、价格、购买人数、店铺名称、店铺所在地信息,抓取入口就是淘宝的搜索页面,这个连接可以通过直接构造参数访问,例如如果搜索 女生连衣裙,就可以直接访问 https://s.taobao.com/search?page=1&q=女生连衣裙&tab=all, 呈现的就是第一页的搜索结果
在这里插入图片描述

presence_of_element_located(locator【目标元素定位】)

根据定位判断页面上是否存在是否存在首个符合定位的元素,如果有(但未必可见)则返回该WebElement

element_to_be_clickable(locator【目标元素定位】)

根据定位判断目标元素是否处于可单击状态(已显示且未被禁用)

  1. 先构造一个 WebDriver 对象,使用的浏览器是 Chrome,然后指定一个关键词,如 iPad
  2. 定义 search_goods() 方法,用于抓取指定页面的商品
  • 2.1 先访问搜索商品的链接,然后判断当前页码,如果大于 1,就进行跳页操作,否则等待页面加载完成【我们使用 WebDriverWait 对象指定等待条件,同时指定一个最长等待时间(这里指定为最长 10 秒),如果在这个时间内成功匹配了等待条件,也就是说页面元素加载出来了就立即返回相应结果并继续向下执行,否则直接抛出超时异常】
  • 2.2 进行翻页操作,首先获取页面底部的页码输入框赋值为 input(清空输入框,将页码填充到输入框中),然后获取确定按钮赋值为 submit
  • 2.3 判断是否跳转到对应页码(判断当前高亮的页码数是当前的页码数即可)【text_to_be_present_in_element_value(locator【目标元素定位】,text【期望包含的文本】):判断目标元素的 Value 属性是否已包含期望的文本】
  • 2.4 待加载出对应页码的商品列表后,再去调用 get_products() 方法进行页面解析

1. 查找商品

def search_goods(start_page, total_pages):
    """
    抓取索引页
    :start_page: 起始页码
    :total_pages: 需要抓取的总页数
    """
    print(f'正在爬取第{start_page}页')
    try:
        url = 'https://s.taobao.com'
        browser.get(url)
        time.sleep(5)  # 强制停止5秒,手动扫码登录

        # 找到搜索输入框
        input_box = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#q")))
        # 找到搜索按钮
        search_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#J_SearchForm > div > div.search-button > button')))

        input_box.send_keys(KEYWORD)  # 在输入框中输入关键词
        search_button.click()        # 点击搜索

        # 搜索商品后会再强制等待10秒(防止被识别为爬虫),如有滑块请手动操作
        time.sleep(10)

        if start_page != 1:
            # 使用JavaScript滚动到页面底部
            browser.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            time.sleep(random.randint(1, 3))  # 滑倒底部后停留 1 - 3 秒

            # 定位到页面底部的页码输入框
            page_box = wait.until(EC.presence_of_element_located((By.XPATH, '//*[@id="pageContent"]/div[1]/div[3]/div[4]/div/div/span[3]/input')))
            print('定位到页面底部的页码输入框成功')
            page_box.clear()  # 清空输入框
            page_box.send_keys(start_page)  # 调用 send_keys() 方法将页码填充到输入框中

            submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#pageContent > div.LeftLay--leftWrap--xBQipVc > div.LeftLay--leftContent--AMmPNfB > div.Pagination--pgWrap--kfPsaVv > div > div > button.next-btn.next-medium.next-btn-normal.next-pagination-jump-go > span')))
            submit.click()

        # 获取每一页的信息
        get_products()

        for i in range(start_page + 1, start_page + total_pages):
            page_turning(i)
    except TimeoutException as e:
        print("搜索商品超时", e)
        search_goods(start_page, total_pages)

2. 爬取商品数据

def get_products(page_num):
    """
    获取对应页码下的所有商品
    :param page_num:页码
    :return:
    """
    print(f"正在提取第{page_num}页的商品信息...")
    time.sleep(random.randint(3, 5))
    simulate_scroll()   # 模拟鼠标慢慢滚动以加载图片
    time.sleep(random.randint(3, 5))
    html = browser.page_source
    doc = pq(html)

    # 提取所有商品的共同父元素的类选择器
    items = doc('div.PageContent--contentWrap--mep7AEm > div.LeftLay--leftWrap--xBQipVc > div.LeftLay--leftContent--AMmPNfB > div.Content--content--sgSCZ12 > div > div').items()
    for item in items:
        title = item.find('.Title--title--jCOPvpf > span').text()   # 定位商品标题
        image = item.find('.MainPic--mainPicWrapper--iv9Yv90 > img').attr('src')   # 定位商品图片地址
        price = item.find('span.Price--priceInt--ZlsSi_M').text() + item.find('span.Price--priceFloat--h2RR0RK').text() + '元'  # 定位价格
        deal = item.find('.Price--realSales--FhTZc7U').text()   # 定位交易量
        shop = item.find('.ShopInfo--shopName--rg6mGmy').text()   # 定位店名

        # 定位所在地信息
        location = item.find('div.Price--priceWrapper--Q0Dn7pN > div:nth-child(4) > span').text() + ' ' + item.find('div.Price--priceWrapper--Q0Dn7pN > div:nth-child(5) > span').text()

        product = {
            '商品图片': image,
            '价格': price,
            '商品简介': title,
            '交易数量': deal,
            '店铺名称': shop,
            '店铺所在地': location
        }
        ws.append([product['商品简介'], product['价格'], product['交易数量'], product['店铺名称'], product['店铺所在地'], product['商品图片']])
        print(product)

3. 模拟鼠标慢滚以加载商品图片

def simulate_scroll():
    """
    模拟鼠标慢慢滚动
    :return:
    """
    # 获取页面高度
    page_height = browser.execute_script("return document.body.scrollHeight")

    # 定义滚动步长和间隔时间
    scroll_step = 5  # 每次滚动的距离
    scroll_delay = 5  # 每次滚动的间隔时间(秒)

    # 模拟慢慢滚动
    current_position = 0
    while current_position < page_height:
        # 计算下一个滚动位置
        next_position = current_position + scroll_step

        # 执行滚动动作
        browser.execute_script(f"window.scrollTo(0, {next_position});")

        # 等待一段时间
        browser.implicitly_wait(scroll_delay)

        # 更新当前滚动位置
        current_position = next_position

4. 页面跳转处理

def page_turning(page_num):
    print(f'正在跳转至第{page_num}页')
    try:
        # 使用JavaScript滚动到页面底部
        browser.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        # 找到下一页的按钮
        submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#pageContent > div.LeftLay--leftWrap--xBQipVc > div.LeftLay--leftContent--AMmPNfB > div.Pagination--pgWrap--kfPsaVv > div > div > button.next-btn.next-medium.next-btn-normal.next-pagination-item.next-next > span')))
        submit.click()
        # 判断页码是否和当前页相等
        wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '#pageContent > div.LeftLay--leftWrap--xBQipVc > div.LeftLay--leftContent--AMmPNfB > div.Pagination--pgWrap--kfPsaVv > div > div > div > button.next-btn.next-medium.next-btn-normal.next-pagination-item.next-current'), str(page_num)))
        print("跳转页面成功")
        get_products()   # 获取该页商品数据
    except TimeoutException:
        page_turning(page_num)

实战案例完整代码(注:需要手动完成扫码登录)

import time
import random

from openpyxl import Workbook

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 pyquery import PyQuery as pq


# 设置为开发者模式,防止被各大网站识别出来使用了Selenium console中输入window.navigator.webdriver 测试
def get_browser():
    browser = webdriver.Chrome()
    # 2.使用js关闭检测机制
    browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
        "source": """
                Object.defineProperty(navigator, 'webdriver', {
                  get: () => undefined
                })
                """
    })

    # 3.返回浏览器对象
    return browser


def page_turning(page_num):
    print(f'正在跳转至第{page_num}页')
    try:
        # 使用JavaScript滚动到页面底部
        browser.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        # 找到下一页的按钮
        submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#pageContent > div.LeftLay--leftWrap--xBQipVc > div.LeftLay--leftContent--AMmPNfB > div.Pagination--pgWrap--kfPsaVv > div > div > button.next-btn.next-medium.next-btn-normal.next-pagination-item.next-next > span')))
        submit.click()
        # 判断页码是否和当前页相等
        wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '#pageContent > div.LeftLay--leftWrap--xBQipVc > div.LeftLay--leftContent--AMmPNfB > div.Pagination--pgWrap--kfPsaVv > div > div > div > button.next-btn.next-medium.next-btn-normal.next-pagination-item.next-current'), str(page_num)))
        print("跳转页面成功")
        get_products(page_num)   # 获取该页商品数据
    except TimeoutException:
        page_turning(page_num)


def simulate_scroll():
    """
    模拟鼠标慢慢滚动以加载商品图片
    :return:
    """
    # 获取页面高度
    page_height = browser.execute_script("return document.body.scrollHeight")

    # 定义滚动步长和间隔时间
    scroll_step = 5  # 每次滚动的距离
    scroll_delay = 5  # 每次滚动的间隔时间(秒)

    # 模拟慢慢滚动
    current_position = 0
    while current_position < page_height:
        # 计算下一个滚动位置
        next_position = current_position + scroll_step

        # 执行滚动动作
        browser.execute_script(f"window.scrollTo(0, {next_position});")

        # 等待一段时间
        browser.implicitly_wait(scroll_delay)

        # 更新当前滚动位置
        current_position = next_position


def get_products(page_num):
    """
    获取对应页码下的所有商品
    :param page_num:页码
    :return:
    """
    print(f"正在提取第{page_num}页的商品信息...")
    time.sleep(random.randint(3, 5))
    simulate_scroll()   # 模拟鼠标慢慢滚动以加载图片
    time.sleep(random.randint(3, 5))
    html = browser.page_source
    doc = pq(html)

    # 提取所有商品的共同父元素的类选择器
    items = doc('div.PageContent--contentWrap--mep7AEm > div.LeftLay--leftWrap--xBQipVc > div.LeftLay--leftContent--AMmPNfB > div.Content--content--sgSCZ12 > div > div').items()
    for item in items:
        title = item.find('.Title--title--jCOPvpf > span').text()   # 定位商品标题
        image = item.find('.MainPic--mainPicWrapper--iv9Yv90 > img').attr('src')   # 定位商品图片地址
        price = item.find('span.Price--priceInt--ZlsSi_M').text() + item.find('span.Price--priceFloat--h2RR0RK').text() + '元'  # 定位价格
        deal = item.find('.Price--realSales--FhTZc7U').text()   # 定位交易量
        shop = item.find('.ShopInfo--shopName--rg6mGmy').text()   # 定位店名

        # 定位所在地信息
        location = item.find('div.Price--priceWrapper--Q0Dn7pN > div:nth-child(4) > span').text() + ' ' + item.find('div.Price--priceWrapper--Q0Dn7pN > div:nth-child(5) > span').text()

        product = {
            '商品图片': image,
            '价格': price,
            '商品简介': title,
            '交易数量': deal,
            '店铺名称': shop,
            '店铺所在地': location
        }
        ws.append([product['商品简介'], product['价格'], product['交易数量'], product['店铺名称'], product['店铺所在地'], product['商品图片']])
        print(product)


def search_goods(start_page, total_pages):
    """
    抓取索引页
    :param page: 页码
    """
    print(f'正在爬取第{start_page}页')
    try:
        url = 'https://s.taobao.com'
        browser.get(url)
        time.sleep(5)  # 强制停止5秒,手动扫码登录

        # 找到搜索输入框
        input_box = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#q")))
        # 找到搜索按钮
        search_button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#J_SearchForm > div > div.search-button > button')))

        input_box.send_keys(KEYWORD)
        search_button.click()

        # 搜索商品后会再强制等待10秒,如有滑块请手动操作
        time.sleep(10)

        if start_page != 1:
            # 使用JavaScript滚动到页面底部
            browser.execute_script("window.scrollTo(0, document.body.scrollHeight);")
            time.sleep(random.randint(1, 3))  # 滑倒底部后停留 1 - 3 秒

            # 定位到页面底部的页码输入框
            page_box = wait.until(EC.presence_of_element_located((By.XPATH, '//*[@id="pageContent"]/div[1]/div[3]/div[4]/div/div/span[3]/input')))
            print('定位到页面底部的页码输入框成功')
            page_box.clear()  # 清空输入框
            page_box.send_keys(start_page)  # 调用 send_keys()方法将页码填充到输入框中

            submit = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#pageContent > div.LeftLay--leftWrap--xBQipVc > div.LeftLay--leftContent--AMmPNfB > div.Pagination--pgWrap--kfPsaVv > div > div > button.next-btn.next-medium.next-btn-normal.next-pagination-jump-go > span')))
            submit.click()

        # 获取每一页的信息
        get_products(start_page)

        for i in range(start_page + 1, start_page + total_pages):
            page_turning(i)
        wb.save(r'淘宝商品数据.xlsx')
    except TimeoutException as e:
        print("搜索商品超时", e)
        search_goods(start_page, total_pages)


def main():
    try:
        page_start = int(input("输入您想开始爬取的页面数:"))
        page_all = int(input("输入您想爬取的总页面数: "))
        search_goods(page_start, page_all)
    except Exception as e:
        print("main函数报错:", e)


if __name__ == '__main__':
    KEYWORD = '女生连衣裙'
    browser = get_browser()
    wait = WebDriverWait(browser, 30)
    wb = Workbook()   # 新建工作簿
    ws = wb.active    # 获取工作表
    ws.append(['商品简介', '价格', '交易数量', '店铺名称', '店铺所在地', '商品图片'])
    main()
  • 16
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

code_lover_forever

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值