之前我用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。