使用Selenium与ddddocr实现京东通过自动验证码商品爬虫的完整指南

使用Selenium与ddddocr实现京东商品爬虫的完整指南

声明:本程序仅作为学习交流使用!侵删

在这篇博客中,我们将详细介绍如何使用Selenium库和ddddocr库来编写一个京东商品爬虫。该爬虫能够自动登录京东,处理滑块验证码,并提取指定页数的商品信息。

本文的示例代码将带你逐步完成这个过程。

前置条件

在开始之前,确保你已经安装了以下Python库:

  • Selenium:用于自动化浏览器操作
  • ddddocr:用于处理验证码
  • Pillow (PIL):用于图像处理
  • chromedriver:用于驱动Chrome浏览器

代码结构

整个爬虫的代码结构如下:

  1. 导入必要的库:我们将导入Selenium及其相关模块,以及处理图像和验证码的库。
  2. 辅助函数:一些辅助函数用于图像处理、验证码滑动距离计算和滑动模拟。
  3. 验证码处理函数:一个专门处理验证码的函数。
  4. 商品信息爬取:主函数负责登录京东,处理验证码,并爬取指定页数的商品信息。

1. 导入必要的库

首先,我们导入所有必要的库和模块:

import 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 as EC
from time import sleep
from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException, TimeoutException
from selenium.webdriver.common.keys import Keys
import ddddocr
import base64
from selenium.webdriver import ActionChains
import random
from PIL import Image
import os

2. 辅助函数

接下来,我们定义了一些辅助函数来处理图像、计算滑动距离以及模拟鼠标移动。

处理验证码图片

这个函数用于将Base64编码的图片转换为PIL图像,并调整其大小:

def handle_img(img_src: str, name: str, size: tuple, name_sized: str):
    if not os.path.exists('images'):
        os.makedirs('images')
    s_img = img_src.replace('data:image/png;base64,', '')
    img_byte = base64.b64decode(s_img)
    with open(f"images/{name}.png", "wb") as f:
        f.write(img_byte)
    img = Image.open(f"images/{name}.png")
    res_img = img.resize(size)
    res_img.save(f"images/{name_sized}.png")
获取滑动距离

我们使用ddddocr库来匹配滑块验证码,并计算出滑块需要移动的距离:

def get_distance(tg_img, bg_img):
    with open(f"images/{tg_img}.png", "rb") as f:
        tg = f.read()
    with open(f"images/{bg_img}.png", "rb") as f:
        bg = f.read()
    det = ddddocr.DdddOcr(det=False, ocr=False, show_ad=False)
    res = det.slide_match(tg, bg, simple_target=True)
    return res['target'][0]
模拟滑动操作

这个函数通过ActionChains模拟了三段速度的鼠标滑动操作,以更好地模仿人类行为,从而提高成功率:

def move_mouse(browser, distance, element):
    has_gone_dist = 0
    remaining_dist = distance
    ActionChains(browser).click_and_hold(element).perform()
    sleep(0.5)
    while remaining_dist > 0:
        ratio = remaining_dist / distance
        if ratio < 0.1:
            span = random.randint(3, 5)
        elif ratio > 0.9:
            span = random.randint(5, 8)
        else:
            span = random.randint(15, 20)
        ActionChains(browser).move_by_offset(span, random.randint(-5, 5)).perform()
        remaining_dist -= span
        has_gone_dist += span
        sleep(random.randint(5, 20) / 100)
    ActionChains(browser).move_by_offset(remaining_dist, random.randint(-5, 5)).perform()
    ActionChains(browser).release(on_element=element).perform()

3. 验证码处理函数

为了处理滑块验证码,我们定义了handle_captcha函数。它主要完成了验证码图片的获取、处理和滑动操作:

def handle_captcha(browser):
    try:
        captcha = browser.find_element(By.XPATH, '//div[@class="JDJRV-bigimg"]/img')
        captcha_img = captcha.get_attribute('src')
        captcha_size = (captcha.size['width'], captcha.size['height'])
        handle_img(captcha_img, 'bg', captcha_size, 'bg_sized')
        wrap = browser.find_element(By.XPATH, '//div[@class="JDJRV-smallimg"]/img')
        wrap_img = wrap.get_attribute('src')
        wrap_size = (wrap.size['width'], wrap.size['height'])
        handle_img(wrap_img, 'sm', wrap_size, 'sm_sized')
        distance = get_distance("sm_sized", "bg_sized")
        print(distance)
        move_mouse(browser, distance, wrap)
        return True
    except NoSuchElementException as msg:
        print("无法获取验证码元素:", msg)
        return False

因为京东后台反人机检测,有时候就算验证码对齐了但依旧无法验证成功,我们只能模拟人类操作,但无法真正实现和人类操作一模一样,所以为了能够通过验证,我们需要进行循环多试几次便可通过验证码验证,后续代码会有实现的操作。

4. 主程序逻辑

在主程序中,我们首先进行登录操作,然后处理验证码,最后开始爬取商品信息。

登录京东并处理验证码
wb = webdriver.Chrome()
wb.set_page_load_timeout(5)
try:
    wb.get('https://www.jd.com/')
except selenium.common.exceptions.TimeoutException:
    pass
wb.implicitly_wait(5)

login_button = WebDriverWait(wb, 10).until(
    EC.element_to_be_clickable((By.CSS_SELECTOR, '#ttbar-login > a.link-login > span.style-red'))
)
login_button.click()

username_field = WebDriverWait(wb, 10).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, '#loginname'))
)
username_field.send_keys('your_username')  # 输入你的用户名

password_field = WebDriverWait(wb, 10).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, '#nloginpwd'))
)
password_field.send_keys('your_password')  # 输入你的密码

submit_button = WebDriverWait(wb, 10).until(
    EC.element_to_be_clickable((By.CSS_SELECTOR, '#loginsubmit'))
)
submit_button.click()
sleep(2)

while True:
    try:
        captcha_element = WebDriverWait(wb, 5).until(
            EC.presence_of_element_located((By.XPATH, '//div[@class="JDJRV-bigimg"]/img'))
        )
        handle_captcha(wb)
        sleep(2)
    except TimeoutException:
        break
搜索并爬取商品信息

在登录成功后,我们进行搜索,并爬取指定页数的商品信息:

try:
    search = WebDriverWait(wb, 10).until(
        EC.element_to_be_clickable((By.CSS_SELECTOR, '#key'))
    )
    search.clear()
    search.send_keys(user_input)
    search_btn = WebDriverWait(wb, 10).until(
        EC.element_to_be_clickable((By.CSS_SELECTOR, '#search > div > div.form > button > i'))
    )
    search_btn.click()
    sleep(10)

    for page in range(1, page_count + 1):
        print(f"正在爬取第 {page} 页")
        items = wb.find_elements(By.XPATH, '//li[@class="gl-item"]')
        for item in items:
            name = get_text_selenium(item, './/div[contains(@class, "p-name")]//em')
            price = get_text_selenium(item, './/div[contains(@class, "p-price")]//i')
            link = get_attribute_selenium(item, './/div[contains(@class, "p-img")]//a', 'href')
            if link and not link.startswith('http'):
                link = 'https:' + link
            print(f"商品名称: {name}")
            print(f"商品价格: {price}")
            print(f"商品链接: {link}")
            print("-" * 50)
        if page < page_count:
            try:
                next_button = WebDriverWait(wb, 10).until(
                    EC.element_to_be_clickable((By.CSS_SELECTOR, '.pn-next'))
                )
                next_button.click()
                sleep(5)
            except:
                print(f"无法加载下一页,已爬取 {page} 页")
                break
finally:
    wb.quit()

5.完整代码

以下是完整的代码示例,每行代码都标有详细的注释:

Python复制代码import selenium  # 导入Selenium库
from selenium import webdriver  # 从Selenium中导入webdriver
from selenium.webdriver.common.by import By  # 导入By类用于定位元素
from selenium.webdriver.support.wait import WebDriverWait  # 导入WebDriverWait类用于显式等待
from selenium.webdriver.support import expected_conditions as EC  # 导入expected_conditions类用于定义等待条件
from time import sleep  # 导入sleep函数用于等待
from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException, TimeoutException  # 导入Selenium常见的异常
from selenium.webdriver.common.keys import Keys  # 导入Keys类用于模拟键盘操作
import ddddocr  # 导入ddddocr库用于处理验证码
import base64  # 导入base64库用于处理Base64编码
from selenium.webdriver import ActionChains  # 导入ActionChains类用于模拟复杂的用户操作
import random  # 导入random库用于生成随机数
from PIL import Image  # 导入PIL库中的Image类用于图像处理
import os  # 导入os库用于文件操作

def handle_img(img_src: str, name: str, size: tuple, name_sized: str):
    """
    对下载的验证码图片进行处理
    :param img_src: 原图片编码
    :param name: 保存名称
    :param size: 调整的大小,元组(width, height)
    :param name_sized: 调整后的图片名
    :return: 无
    """
    if not os.path.exists('images'):  # 检查images目录是否存在
        os.makedirs('images')  # 创建images目录
    s_img = img_src.replace('data:image/png;base64,', '')  # 去除Base64前缀
    img_byte = base64.b64decode(s_img)  # 解码Base64编码的图片
    with open(f"images/{name}.png", "wb") as f:  # 以二进制写入方式打开文件
        f.write(img_byte)  # 将图片写入文件
    img = Image.open(f"images/{name}.png")  # 打开图片文件
    res_img = img.resize(size)  # 调整图片大小
    res_img.save(f"images/{name_sized}.png")  # 保存调整后的图片

def get_distance(tg_img, bg_img):
    """
    获取滑动距离
    :param bg_img: 底层大图片
    :param tg_img: 滑块小图片
    :return: 返回距离
    """
    with open(f"images/{tg_img}.png", "rb") as f:  # 打开目标图片文件
        tg = f.read()  # 读取文件内容
    with open(f"images/{bg_img}.png", "rb") as f:  # 打开背景图片文件
        bg = f.read()  # 读取文件内容
    det = ddddocr.DdddOcr(det=False, ocr=False, show_ad=False)  # 创建ddddocr对象
    res = det.slide_match(tg, bg, simple_target=True)  # 使用ddddocr进行滑块匹配
    return res['target'][0]  # 返回滑动距离

def move_mouse(browser, distance, element):
    """
    轨迹模拟方法(三段随机速度)成功几率高
    :param browser: 浏览器driver对象
    :param distance: 移动距离
    :param element: 移动的元素
    :return: 无返回值
    """
    has_gone_dist = 0  # 已移动距离
    remaining_dist = distance  # 剩余距离
    ActionChains(browser).click_and_hold(element).perform()  # 点击并按住元素
    sleep(0.5)  # 等待0.5秒
    while remaining_dist > 0:  # 当剩余距离大于0时
        ratio = remaining_dist / distance  # 计算已移动的比例
        if ratio < 0.1:  # 如果已移动比例小于0.1
            span = random.randint(3, 5)  # 随机生成移动距离
        elif ratio > 0.9:  # 如果已移动比例大于0.9
            span = random.randint(5, 8)  # 随机生成移动距离
        else:  # 如果已移动比例在0.1到0.9之间
            span = random.randint(15, 20)  # 随机生成移动距离
        ActionChains(browser).move_by_offset(span, random.randint(-5, 5)).perform()  # 移动鼠标
        remaining_dist -= span  # 更新剩余距离
        has_gone_dist += span  # 更新已移动距离
        sleep(random.randint(5, 20) / 100)  # 随机等待
    ActionChains(browser).move_by_offset(remaining_dist, random.randint(-5, 5)).perform()  # 移动剩余距离
    ActionChains(browser).release(on_element=element).perform()  # 释放鼠标

def handle_captcha(browser):
    """
    处理验证码的函数
    :param browser: 浏览器对象
    :return: 验证码处理成功返回True,否则返回False
    """
    try:
        captcha = browser.find_element(By.XPATH, '//div[@class="JDJRV-bigimg"]/img')  # 获取验证码图片背景大图
        captcha_img = captcha.get_attribute('src')  # 获取验证码图片的src属性
        captcha_size = (captcha.size['width'], captcha.size['height'])  # 获取验证码图片的大小
        handle_img(captcha_img, 'bg', captcha_size, 'bg_sized')  # 处理验证码背景图片
        wrap = browser.find_element(By.XPATH, '//div[@class="JDJRV-smallimg"]/img')  # 获取滑块小图
        wrap_img = wrap.get_attribute('src')  # 获取滑块图片的src属性
        wrap_size = (wrap.size['width'], wrap.size['height'])  # 获取滑块图片的大小
        handle_img(wrap_img, 'sm', wrap_size, 'sm_sized')  # 处理滑块图片
        distance = get_distance("sm_sized", "bg_sized")  # 计算滑动距离
        print(distance)  # 打印滑动距离
        move_mouse(browser, distance, wrap)  # 进行滑动
        return True  # 返回True表示验证码处理成功
    except NoSuchElementException as msg:  # 如果出现NoSuchElementException异常
        print("无法获取验证码元素:", msg)  # 打印异常信息
        return False  # 返回False表示验证码处理失败

def get_text_selenium(driver, xpath):
    """
    获取元素文本内容
    :param driver: 浏览器driver对象
    :param xpath: 元素的XPath路径
    :return: 返回元素的文本内容,如果找不到元素返回空字符串
    """
    try:
        return driver.find_element(By.XPATH, xpath).text.strip()  # 获取元素的文本内容并去除首尾空格
    except (NoSuchElementException, StaleElementReferenceException):  # 如果出现NoSuchElementException或StaleElementReferenceException异常
        return ""  # 返回空字符串

def get_attribute_selenium(driver, xpath, attribute):
    """
    获取元素的指定属性值
    :param driver: 浏览器driver对象
    :param xpath: 元素的XPath路径
    :param attribute: 要获取的属性名称
    :return: 返回元素的属性值,如果找不到元素或属性返回空字符串
    """
    try:
        return driver.find_element(By.XPATH, xpath).get_attribute(attribute)  # 获取元素的指定属性值
    except (NoSuchElementException, StaleElementReferenceException):  # 如果出现NoSuchElementException或StaleElementReferenceException异常
        return ""  # 返回空字符串

user_input = input("请输入要搜索内容:")  # 获取用户输入的搜索内容
page_count = int(input("请输入要爬取的页数:"))  # 获取用户输入的要爬取的页数

wb = webdriver.Chrome()  # 创建Chrome浏览器对象
wb.set_page_load_timeout(5)  # 设置页面加载超时时间为5秒
try:
    wb.get('https://www.jd.com/')  # 打开京东首页
except selenium.common.exceptions.TimeoutException:
    pass  # 如果加载超时,继续执行
wb.implicitly_wait(5)  # 设置隐式等待时间为5秒

login_button = WebDriverWait(wb, 10).until(
    EC.element_to_be_clickable((By.CSS_SELECTOR, '#ttbar-login > a.link-login > span.style-red'))
)  # 等待并找到登录按钮
login_button.click()  # 点击登录按钮

username_field = WebDriverWait(wb, 10).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, '#loginname'))
)  # 等待并找到用户名输入框
username_field.send_keys('your_username')  # 输入你的用户名

password_field = WebDriverWait(wb, 10).until(
    EC.presence_of_element_located((By.CSS_SELECTOR, '#nloginpwd'))
)  # 等待并找到密码输入框
password_field.send_keys('your_password')  # 输入你的密码

submit_button = WebDriverWait(wb, 10).until(
    EC.element_to_be_clickable((By.CSS_SELECTOR, '#loginsubmit'))
)  # 等待并找到登录提交按钮
submit_button.click()  # 点击提交按钮
sleep(2)  # 等待2秒

while True:  # 循环处理验证码
    try:
        captcha_element = WebDriverWait(wb, 5).until(
            EC.presence_of_element_located((By.XPATH, '//div[@class="JDJRV-bigimg"]/img'))
        )  # 等待验证码元素出现
        handle_captcha(wb)  # 调用handle_captcha函数处理验证码
        sleep(2)  # 等待2秒
    except TimeoutException:  # 如果等待超时,表示验证码已经处理完毕
        break  # 退出循环

try:
    search = WebDriverWait(wb, 10).until(
        EC.element_to_be_clickable((By.CSS_SELECTOR, '#key'))
    )  # 等待并找到搜索框
    search.clear()  # 清空搜索框
    search.send_keys(user_input)  # 输入搜索关键字
    search_btn = WebDriverWait(wb, 10).until(
        EC.element_to_be_clickable((By.CSS_SELECTOR, '#search > div > div.form > button > i'))
    )  # 等待并找到搜索按钮
    search_btn.click()  # 点击搜索按钮
    sleep(10)  # 等待搜索结果加载

    for page in range(1, page_count + 1):  # 循环遍历指定页数
        print(f"正在爬取第 {page} 页")  # 打印当前爬取的页数
        items = wb.find_elements(By.XPATH, '//li[@class="gl-item"]')  # 查找当前页的所有商品项
        for item in items:  # 遍历所有商品项
            name = get_text_selenium(item, './/div[contains(@class, "p-name")]//em')  # 获取商品名称
            price = get_text_selenium(item, './/div[contains(@class, "p-price")]//i')  # 获取商品价格
            link = get_attribute_selenium(item, './/div[contains(@class, "p-img")]//a', 'href')  # 获取商品链接
            if link and not link.startswith('http'):  # 如果链接不完整,补全链接
                link = 'https:' + link
            print(f"商品名称: {name}")  # 打印商品名称
            print(f"商品价格: {price}")  # 打印商品价格
            print(f"商品链接: {link}")  # 打印商品链接
            print("-" * 50)  # 分隔符
        if page < page_count:  # 如果还没有爬取完指定页数
            try:
                next_button = WebDriverWait(wb, 10).until(
                    EC.element_to_be_clickable((By.CSS_SELECTOR, '.pn-next'))
                )  # 等待并找到下一页按钮
                next_button.click()  # 点击下一页按钮
                sleep(5)  # 等待新一页加载
            except:  # 如果无法加载下一页
                print(f"无法加载下一页,已爬取 {page} 页")  # 打印错误信息
                break  # 退出循环
finally:
    wb.quit()  # 关闭浏览器

总结

通过这篇博客,我们详细介绍了如何使用Selenium和ddddocr库来实现一个京东商品爬虫。这个爬虫能够自动登录京东账户,处理滑块验证码,并爬取指定页数的商品信息。

你可以根据自己的需求,进一步优化和扩展该爬虫。例如,保存爬取的数据到文件或数据库,或扩展到其他电商网站。

希望这篇博客对你有所帮助!如果你有任何问题或建议,欢迎留言讨论。

注:自动验证码识别参考资料

https://blog.csdn.net/kayotin/article/details/135678539
https://github.com/zjy6622/python-/blob/master/selenium京东模拟登陆.py

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值