天天看点

selenium淘宝爬虫使用selenium做淘宝商品爬虫

selenium淘宝爬虫

  • 使用selenium做淘宝商品爬虫
    • 1、准备工作
    • 2、页面分析
    • 3、代码实现
      • 3-1、模拟登陆
      • 3-2、商品列表页
      • 3-3、获取商品信息
      • 3-4、数据库设计
      • 3-5、爬虫执行
      • 3-6、爬虫执行结果
    • 4、待解决的问题
    • 5、总结

使用selenium做淘宝商品爬虫

最近在学习崔庆才老师的《Python3网络爬虫开发实战》,第七章《动态渲染页面爬取》的使用selenium爬取淘宝商品,由于该书出版时间已久,淘宝的反爬机制做了其他改动,书中代码需要改进,特发此文,记录自己学习过程中遇到的问题及解决方法。

1、准备工作

确认已经安装chrome浏览器,并配置好对应版本的ChromeDriver,另外还需要正确安装selenium。

ChromeDriver各版本可在 ChromeDriver各版本下载 中查找并下载。下载完解压到你Anaconda安装目录下的Scripts目录即可。

2、页面分析

打开 淘宝主页 在搜索框键入你想要搜索的商品,进入商品列表页。比如搜“辣条”,得到以下页面:

selenium淘宝爬虫使用selenium做淘宝商品爬虫

每个商品包含该商品的基本信息,比如价格、图片、名称、店铺信息等,我们的目的就是将它们爬取下来。

拉到页面底部,会出现如下分页导航,我们爬取多页商品时,翻页需要用到它。

selenium淘宝爬虫使用selenium做淘宝商品爬虫

3、代码实现

这部分是整个项目的代码实现阶段。

3-1、模拟登陆

在学习书中代码时,书中第一步就是直接到商品列表页,但是仙子啊已经行不通了,因为淘宝改成了需要登陆账号才能访问商品列表页,因此需要模拟登陆淘宝。

首先创建account.py文件,保存自己的账号信息。之后就可以编辑登陆函数来实现登陆操作了。但是实际过程并没有这么简单,比如我遇到了以下问题。

selenium淘宝爬虫使用selenium做淘宝商品爬虫

出现滑块验证,并且无论我怎么滑,都无法通过验证。上网查询解决方案,理由是淘宝检测出了我们浏览器是通过selenium打开的,因此无法访问。selenium打开的浏览器有一个特征,就是window.navigator.webdriver的值为true,而正常打开的浏览器,该值是undifine。修改webdriver为undifine试试。

根据 Chrome 79以后版本Selenium中window.navigator.webdriver 值无法更改的解决方法 我们插入以下代码:

options = ChromeOptions()
# 开启实验性功能
options.add_argument('--start-maximized')  # 最大化运行(全屏窗口),不设置,取元素会报错
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_experimental_option('useAutomationExtension', False)
browser = webdriver.Chrome(options=options)
script = '''
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
'''
browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {"source": script})
wait = WebDriverWait(browser, 5)
           

试试运行结果:

selenium淘宝爬虫使用selenium做淘宝商品爬虫

遗憾失败,结果与未插入一样。可能是chrome版本升级带来的问题,试试查找chrome88.0的解决方法。

在 chrome88.0版本修改selenium的window.navigator.webdriver 文章中找到可行的解决方法。插入以下代码:

运行试试结果:

selenium淘宝爬虫使用selenium做淘宝商品爬虫

达到目的。但是下一步也存在问题,登陆完,有个验证码拦截页面。离谱,第一天运行还没有呢,第二天就出现了。

selenium淘宝爬虫使用selenium做淘宝商品爬虫

不过这个拦截比较简单,手动验证即可,验证完直接到达商品列表页。

本节代码如下:

KEYWORD = '辣条'
USER = account.USER
PASSWORD = account.PASSWORD

options = ChromeOptions()
# 开启实验性功能
options.add_argument('--start-maximized')  # 最大化运行(全屏窗口),不设置,取元素会报错
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_experimental_option('useAutomationExtension', False)
options.add_argument("--disable-blink-features=AutomationControlled")
browser = webdriver.Chrome(options=options)
wait = WebDriverWait(browser, 5)


def login():
    url = 'https://s.taobao.com/search?q=' + quote(KEYWORD)
    browser.get(url)
    # 登录
    user = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#fm-login-id')))
    user.send_keys(USER)
    password = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#fm-login-password')))
    password.send_keys(PASSWORD)
    submit = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.fm-btn > button')))
    submit.click()
    time.sleep(1)
           

3-2、商品列表页

到达商品列表页后的操作,书上的代码并没有任何问题这里不再缀述,代码如下:

def index_page(page):
    '''

    :param page: 页码信息
    :return:
    '''
    try:
        # 翻页
        if page > 1:
            page_input = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-pager div.form > input')))
            page_submit = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-pager div.form > span.btn.J_Submit')))
            page_input.clear()
            page_input.send_keys(page)
            page_submit.click()
            time.sleep(3)

        # 判定是否成功翻页
        if wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '#mainsrp-pager ul.items li.item.active > span'), str(page))):
            wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-itemlist div.items .item')))
            get_products(page)
    except TimeoutException:
        index_page(page)
           

值得一提的是,这里的翻页操作是直接在分页列表的额输入框输入页码进行翻页的。

3-3、获取商品信息

这一步是提取每个商品的信息并保存到数据库,书本上用的是MongoDB,我用的是MySQL。

def get_products(page):
    html = browser.page_source
    doc = pq(html)
    items = doc('#mainsrp-itemlist div.items .item').items()
    items = list(items)
    # print(len(items))
    if page < 10:
        page = '0' + str(page)
    for i in range(len(items)):
        products = {
            'id': datetime.now().strftime('%Y%m%d') + page + str(i+1),
            'title': items[i].find('.title').text(),
            'price': items[i].find('.price').text(),
            'deal': items[i].find('.deal-cnt').text(),
            'shop': items[i].find('.shop').text(),
            'location': items[i].find('.location').text(),
            'image': items[i].find('.pic .img').attr('data-src'),
        }
        # print(products)
        save_mysql.insert_data(data=products, dbname=DBNAME, table=KEYWORD)
           

提取出来的商品信息是没有id的,我在这里利用时间、页码和商品索引给它们设计了一个id,方便查询。

3-4、数据库设计

本节是储存商品的数据库设计。

import pymysql
import account


MYPASSWORD = account.MYSQL_PASSWORD


def create_db(dbname):
    '''
    创建数据库
    :param dbname: 数据库名
    :return: 
    '''
    db = pymysql.connect(host='localhost', user='root', passwd=MYPASSWORD, port=3306)
    cursor = db.cursor()
    sql = 'CREATE DATABASE {dbname} DEFAULT CHARACTER SET utf8'.format(dbname=dbname)
    cursor.execute(sql)
    db.close()


def create_table(dbname, table_name):
    '''
    创建表
    :param dbname: 数据库名
    :param table_name: 数据表名
    :return: 
    '''
    db = pymysql.connect(host='localhost', user='root', passwd=MYPASSWORD, port=3306, db=dbname)
    cursor = db.cursor()
    sql = 'CREATE TABLE IF NOT EXISTS {table_name} (id VARCHAR(255) NOT NULL, title VARCHAR(255) NOT NULL, price VARCHAR(255) NOT NULL, deal VARCHAR(255), shop VARCHAR(255) NOT NULL, location VARCHAR(255), image VARCHAR(255) NOT NULL, PRIMARY KEY(id))'.format(table_name=table_name)
    cursor.execute(sql)
    db.close()


def insert_data(data, dbname, table):
    '''
    插入数据
    :param data: 数据
    :param dbname: 数据库
    :param table: 数据标
    :return: 
    '''
    db = pymysql.connect(host='localhost', user='root', passwd=MYPASSWORD, port=3306, db=dbname)
    cursor = db.cursor()
    keys = ','.join(data.keys())
    values = ','.join(['%s'] * len(data))
    sql = 'INSERT INTO {table}({keys}) VALUES ({values})'.format(table=table, keys=keys, values=values)
    try:
        if cursor.execute(sql, tuple(data.values())):
            # print('Done')
            db.commit()
    except:
        print(pymysql.DatabaseError.with_traceback())
        db.rollback()
    db.close()
           

3-5、爬虫执行

为了不给淘宝网站成太大负载,这里就只爬取两页。

if __name__ == '__main__':
    # max_page = int(input('请输入你想要爬取的页数:'))
    login()
    max_page = 2
    for page in range(1, max_page+1):
        index_page(page=page)
        time.sleep(2)
           

3-6、爬虫执行结果

爬虫会自动登录,手动验证完后,会自动执行爬取和翻页操作。

selenium淘宝爬虫使用selenium做淘宝商品爬虫

打开数据库,查看数据是否保存。

selenium淘宝爬虫使用selenium做淘宝商品爬虫

成功保存。

4、待解决的问题

其实在这个项目中我遇到的问题不止这么点,我还遇到了验证码拦截界面死活验证不成功的,然后参考了 selenium 反爬虫之跳过淘宝滑块验证功能的实现代码 这篇文章,修改了chromewebdriver.exe的**$cdc_asdjflasutopfhvcZLmcfl_**参数,才能验证成功。但是离谱的是,为了写这篇博客,我换回了初始的chromewebdriver.exe,想以此引出我遇到的问题,结果居然能验证成功了,离谱。希望路过的大牛能帮我解答一二。

还有另外的问题,就是为了不手动验证,我加入了自动破解滑块验证码的功能,但是一直验证失败。可能是我自动滑块的功能被识别出是机器人吧,怎么调参都验证失败。一下是我的破解代码,希望路过的大牛能教我怎么设置参数才能让爬虫表现的像个人。

def get_track(distance):
    track = []
    current = 0
    t = 3
    mid = distance * (7/8)
    v0 = 0
    a_list = [10, 6, 5, 8]
    while current < distance:
        if current < mid:
            a = random.choice(a_list)
        else:
            a = -10
        v = v0 + a * t
        X = v0 * t + 0.5 * a * (t**2)
        v0 = v
        current += X
        track.append(round(X))
    return track


def move_slider():
    time.sleep(3)
    slider = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#nc_1_n1z')))
    slider_bg = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '#nc_1__scale_text')))
    # time.sleep(2)
    size = slider.size
    bg_size = slider_bg.size
    distance = bg_size['width'] - size['width']
    tracks = get_track(distance)
    ActionChains(browser).click_and_hold(slider).perform()
    for x in tracks:
        ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform()
    ActionChains(browser).release().perform()
           

5、总结

本次实验是一个不错的爬虫实验,熟练使用selenium是爬虫工程师必备的技能。希望这篇文章可以对小白有点帮助,也希望路过的大牛能帮忙解决一下我遇到的问题,学无止境,你我共勉。