使用pyppeteer爬取淘宝商品

之前我用pyppeteer绕过了淘宝登录时对于web driver的检测,但是这并不意味这登录

后就没有检测了,今天我就来以爬取搜索关键字得到的商品名称为例操作一下。

整个过程有 4 步:1.登录,2.输入关键字并点击搜索,3.滑到最底部并获取数据,4.点击下一页,然后重复步骤 3,4 直到没有下一页(实际上一个账号并不能每一页全部爬完,要想全部爬完可能要买或者借账号,下面的教程我只爬前几页的数据)。然后就是要搭好一个框架,为了让程序看起来简单,我就使用面向对象的设计方法来设计这个程序,大致代码如下:

class TaoBaoSpider:
    async def login(self):
        pass
    async def search(self):
        pass
    async def crawl(self):
        pass

因为pyppeteer是通过async和await异步机制来实现的,所以方法必须都是异步的,所以我在每个方法定义之前都加上了async关键字。

登录

登录有两种方式,一种是手机扫码登录,另一种是输入账号密码登录,为了尽可能简单和尽可能的不被检测,我使用手机扫码登录。既然使用手机扫码登录,那么 login 方法的实现就非常简单,只要 sleep 一会就行了。但这里需要注意的是,不能使用 time 模块的 sleep 函数(这是同步阻塞接口的等待,不能在异步函数中出现),我们需要一个异步等待,这个异步等待就是 asyncio 模块的 sleep 函数,调用时需要用关键字 await 修饰,如下所示。

from asyncio import sleep

class TaoBaoSpider:
    @staticmethod
    async def login():
        await sleep(10)

    async def search(self):
        pass

    async def crawl(self):
        pass

这里我设置等待时间为 10 秒,在这 10 秒钟完成手机扫码登录应该够了,如果觉得时间紧迫可以把 10 改成更大的数。

下面我们需要进行测试,在测试之前,我们要先写好这个类的构造方法,来初始化一些属性,因为在 search 和 crawl 方法中都需要使用浏览器对象,所以我们要初始化一个浏览器对象,如下所示。

self.browser = await launch(headless=False, args=['--disable-infobars', f'--window-size={self.width},{self.height}'])

我们都知道,await 关键字必须在 async 修饰的函数体内,有些人可能会想到按照下面这种写法:

async def __init__(self)

这样写是错的,如图所示。

稍微翻译一下,说函数“__init__”不可以是异步的!那么我们就需要单独定义一个异步方法来完成异步的初始化。这个异步方法我就定义为 init 了。下面就主要编写 __init__ 和 init 两个方法。

from asyncio import sleep, get_event_loop
from pyppeteer import launch

class TaoBaoSpider:
    def __init__(self):
        self.width, self.height = 1500, 800
        get_event_loop().run_until_complete(self.init())
        get_event_loop().run_until_complete(self.login())
        get_event_loop().run_until_complete(self.search())
        get_event_loop().run_until_complete(self.crawl())

    async def init(self):
        # noinspection PyAttributeOutsideInit
        self.browser = await launch(headless=False, 
                            args=['--disable-infobars', f'--window-size={self.width},{self.height}'])
        # noinspection PyAttributeOutsideInit
       self.page = await self.browser.newPage()
       await self.page.setViewport({'width': self.width, 'height': self.height})
       await self.page.goto('https://login.taobao.com/member/login.jhtml?redirectURL=https://www.taobao.com/')
       await self.page.evaluate('()=>{Object.defineProperties(navigator,{webdriver:{get:()=>false}})}')

   @staticmethod
   async def login():
       await sleep(10)

   async def search(self):
       pass

   async def crawl(self):
       pass

运行程序会弹出一个Chromium浏览器,浏览器显示的就是淘宝登录页面,手机扫码可以成功登录,没有被检测,登录部分测试完毕,下面我们主要编写一下search方法。

搜索

在编写搜索方法之前,我们来想一个问题,刚登录就搜索是不是太快了?我们都知道,访问太快容易被 BAN 的,所以我们需要降低访问速度。这个可以通过异步等待来实现。

下面又来了一个关键性的问题,我具体应该等待多久?时间太长效率就太低,时间太短容易被检测,我们可以想一下自己手动访问淘宝速度是怎样的,我感觉是1到4秒进行一次点击啥的,有些人就想当然认为

这就太简单了,直接构造方法中初始化不就完了吗?如下所示:

self.sleep_time = 1+random()*3

其实这样写是错的,因为这个字段值在中途不会被重新初始化,也就是每两个事件之间都是相同的时间间隔,这样不被检测出来就有鬼,毕竟人操作不可能这么机械化,那么我们需要在每次调用它的时候都能修改它的值,既然这个值在不断的变化,我们可以把它定义成方法,可是定义成方法后面的每次调用都要一对括号,这简直是太繁琐了。我们可以使用 property 来装饰这个方法,在调用的时候不需要加括号了,下面我实现搜索的方法。

from asyncio import sleep, get_event_loop
from pyppeteer import launch
from random import random

class TaoBaoSpider:
    def __init__(self):
        self.width, self.height = 1500, 800
        get_event_loop().run_until_complete(self.init())
        get_event_loop().run_until_complete(self.login())
        get_event_loop().run_until_complete(self.search())
        get_event_loop().run_until_complete(self.crawl())

    async def init(self):
       # noinspection PyAttributeOutsideInit
       self.browser = await launch(headless=False,
                                   args=['--disable-infobars', f'--window-size={self.width},{self.height}'])
       # noinspection PyAttributeOutsideInit
       self.page = await self.browser.newPage()
       await self.page.setViewport({'width': self.width, 'height': self.height})
       await self.page.goto('https://login.taobao.com/member/login.jhtml?redirectURL=https://www.taobao.com/')
       await self.page.evaluate('()=>{Object.defineProperties(navigator,{webdriver:{get:()=>false}})}')

   @staticmethod
   async def login():
       await sleep(10)

   @property
   def sleep_time(self):
       return 1+random()*3

    async def login():
        await sleep(10)

    @property
    def sleep_time(self):
        return 1+random()*3

    async def search(self):
        await self.page.click('#q')
        await sleep(self.sleep_time)
        await self.page.keyboard.type('机械革命')
        await sleep(self.sleep_time)
        await self.page.click('#J_TSearchForm > div.search-button > button')
        await sleep(self.sleep_time)

async def crawl(self):
    pass

if __name__ == '__main__':
    TaoBaoSpider()

在这里我搜索的关键字直接赋了初值,大家可以自己修改成输入传参的形式,这很简单,我就不讲了,下面是最关键的步骤,爬取数据。

爬取数据

爬取数据我们就爬一下搜索到的商品名称,下图中价格下面的两行字。

通过审查元素我们可以找到每一段字的对应的HTML的源码形式,如下图所示:

既然a标签下还有标签,那么我们就需要做出二次过滤,第一次提取a标签下的内容,第二次把第一次提取的数据的标签和空白字符去掉。所以需要些两个正则,首先是提取a标签下的内容,经过不停地比对,可以得出最后的正则:

pattern = compile(r'<a id=".*?" class="J_ClickStat".*?>(.*?)</a>',S)

接着是用来替换的正则: 

repl_pattern = compile(r'<.*?>|\s+')

接下来我们尝试获取前5页的数据。先到底部(不能一蹴而就,一定要模拟人的操作。这里使用匀加速来实现,加速度是一个随机值,不随机可能会被检测),然后获取页面源码进行数据筛选。最后跳到下一页,重复之前的操作。下面直接给出完整的源代码。

from asyncio import sleep, get_event_loop
from pyppeteer import launch
from random import random
from re import compile, S

class TaoBaoSpider:
    def __init__(self):
        self.width, self.height = 1500, 800
        get_event_loop().run_until_complete(self.init())
        get_event_loop().run_until_complete(self.login())
        get_event_loop().run_until_complete(self.search())
        get_event_loop().run_until_complete(self.crawl())

    async def init(self):
        # noinspection PyAttributeOutsideInit
       self.browser = await launch(headless=False,
                                   args=['--disable-infobars', f'--window-size={self.width},{self.height}'])
       # noinspection PyAttributeOutsideInit
       self.page = await self.browser.newPage()
       await self.page.setViewport({'width': self.width, 'height': self.height})
       await self.page.goto('https://login.taobao.com/member/login.jhtml?redirectURL=https://www.taobao.com/')
       await self.page.evaluate('()=>{Object.defineProperties(navigator,{webdriver:{get:()=>false}})}')

   @staticmethod
    async def login():
        await sleep(10)

    @property
    def sleep_time(self):
        return 1+random()*3

async def search(self):
    await self.page.click('#q')
    await sleep(self.sleep_time)
    await self.page.keyboard.type('机械革命')
    await sleep(self.sleep_time)
    await self.page.click('#J_TSearchForm > div.search-button > button')
    await sleep(self.sleep_time)

async def crawl(self):
    pattern = compile(r'<a id=".*?" class="J_ClickStat".*?>(.*?)</a>', S)
       repl_pattern = compile(r'<.*?>|\s+')
       for i in range(5):
           # document.body.clientHeight
           height = await self.page.evaluate('document.body.clientHeight')
           scrolled_height = 0
           a = 1+random()
           t = 1
           # window.scrollTo(width, height)
           while scrolled_height < height:
               scrolled_height = int(1/2*a*t**2)  # x=v0*t+1/2*a*t**2,v0=0
               await self.page.evaluate(f'window.scrollTo(0,{scrolled_height})')
               t += 1
           await sleep(self.sleep_time)
           html = await self.page.content()
           results = pattern.findall(html)
           for result in results:
               result = repl_pattern.sub('', result)
               print(result)
           print()
           await sleep(self.sleep_time)
           await self.page.click('#mainsrp-pager > div > div > div > ul > li.item.next > a')
           await sleep(self.sleep_time)
       await sleep(self.sleep_time)

if __name__ == '__main__':
    TaoBaoSpider()

运行结果如图所示。

可以发现显示在网页上的数据被爬了下来,下面来总结一些应付这种反爬特别严的网站的一些技巧:

1.模拟人的操作,每次请求都要随机等待一会。

2.如果需要登录,尽量手动登录,自动登录可能会被检测。

3.一个账号(如果要登录)或一个IP访问次数不要太多,如果要爬很多数据可以使用多个账号(如果要登录)或者对个IP。

猜你喜欢

转载自blog.csdn.net/zhangge3663/article/details/108202145