爬虫笔记

request

如何使用:(requests模块的编码流程)

  1. 指定url
    • UA伪装
    • 请求参数的处理
  2. 发起请求
  3. 获取响应数据
  4. 持久化存储

Ajax

是创建交互式网页应用的网页开发技术的一种。
Ajax = 异步 JavaScript 和 XML 或者是 HTML(标准通用标记语言的子集)。可以用于创建快速动态网页的技术。
在无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。

动态加载数据的简单判断方法
  • 发起一个请求(比如一次查询),如果发现页面的url没有改变,但显示内容改变了,则为Ajax请求
  • 找到对应url的数据包,在其response中可以找到相关的所要查找的内容时,则说明内容不是动态加载
    相反,如果页面有,但页面对应的response没有,则说明对应内容动态加载

GET与POST方法区别

GET方法一般用于查询并获取信息,这意味着它是幂等的(对同一个url的多个请求,返回结果完全一样),因为没有修改资源状态,所以它是安全的。而POST一般用于更新资源信息,既不是幂等,也不是安全的。

  1. 缓存。
    GET方法请求的数据会被浏览器缓存下来,会留下历史记录。而POST方法提交的数据不会被浏览器缓存下来,不会留下历史记录。

  2. 数据。
    客户端采用GET方法时,它是把要发送的数据添加url后面,就是把数据放在http协议的头中,在url后面用“?”连接数据,而数据中的各个变量用“&”连接,因为传输的数据就在url中,而且又被缓存在浏览器中,所以传输的数据安全差。而客户端采用POST方法时,它把数据放在http请求报文的消息体中,因为传输的数据在消息体中,因此数据的安全性高些,但是用抓包软件进行抓包,也可以看到传递的数据内容。

  3. 长度。
    http协议没有对url的长度进行限制,但是特定的浏览器和服务器都会对url的长度存在限制,所以传输的数据量有限。http协议没有对http请求报文中的消息体的大小限制,取决于服务器的处理程序的能力。

存储json数据

注意:数据中有中文,不可以使用ASCII码编码
如果请求后返回一个json串中有中文,如果要存储时,使用
json.dump(obj, fp, ensure_ascii=false) # ensure_ascii=false用来保证不使用ascii编码

得到相应后获得对应数据 .json .text .content

观察Headers中Response Headers中的Content-Type

.json: content-type: application/json

获取响应数据:json()方法返回的是obj(如果确认响应数据是json类型的,才可以使用json())
dic_obj = response.json()

.text: Content-Type: text/plain; charset=utf-8

txt = response.text

  • 原网址相应数据为:
    在这里插入图片描述
    如果想看这个json数据对应的具体含义,可以在json格式化工具中查看

如果对json数据使用 .text 获取,则会得到
在这里插入图片描述
而对json数据正确使用 .json() 会得到(可以自动的得到对应的中文)
在这里插入图片描述

  • 如果原网站为
    在这里插入图片描述
    使用 .text 和 .json() 都可以,不过 .json() 可以让返回变成为python字典类型(因为图中相应数据两边有’{’,’}’),这样可以方便的使用对字典的操作取部分数据
    使用 .text 则需要使用split函数进行操作来取部分数据
.content: 如果需要返回的为二进制数据(例如图片)
resp = requests.get("...")   
resp.content    // 返回的是一个原生字符串,是bytes类型

乱码问题

得到的相应或者某个标签中的文本为乱码时,可以使用一下方法

  1. 对整个相应进行编码
    response.encoding = 'utf-8'
  2. 对乱码对应的某组值单独编码
    img_name = img_name.encode('iso-8859-1').decode('gbk')

聚焦爬虫

爬取页面中指定的内容(数据解析)
数据解析原理概述: 解析的局部的文本内容都会在标签之间或者标签对应的属性中进行存储

  1. 进行指定标签的定位
  2. 标签或者标签对应的属性中存储的数据值进行提取(解析)

法1 正则表达式

用来判断字符串是否满足一定标准
在这里插入图片描述
例子:
例子
例子

Re库

Re库

模式选项

模式选项

python正则表达式例子
# 创建了模式对象
pat = re.compile("str")  # 此处str 是正则表达式,用来验证其他字符
m = pat.search("str")  # 此处str是被校验的内容
# 不创建模式对象,直接匹配
m = re.search("str_pat", "str") 
# 前面字符串是正则表达式
# 后面字符串是被匹配的内容

法2 bs4 — 仅可找标签相关内容

思想
  1. 实例化一个BeautifulSoup对象,并且将页面源码数据加载到对象中
  2. 通过调用BeautifulSoup对象中相关的属性或者方法进行标签的定位和数据提取
对象的实例化
from bs4 import BeautifulSoup
# 法1:将本地的html文档中的数据加载到该对象中
fp = open('./test.html','r',encoding='utf-8')  # 文件描述符
soup = BeautifulSoup(fp,'lxml')  # 使用lxml解析器进行解析

# 法2:从互联网中获取页面的源码加载到该对象中
page_text = response.text
soup = BeautifulSoup(page_text, 'lxml')
对象的使用 — 获取标签
.tagName 仅第一个出现

soup.tagName # 返回文档中第一次出现 叫tagName的标签

find 仅第一个出现 与 findall 全部的

soup.find('tagName') # 与soup.tagName返回相同

# 属性定位:根据属性进行选择对应属性值的标签
# 例如:找到对应class属性为song的div标签
soup.find('div',class_/id/attr='song')  # 如果属性为class属性,需要使用class_,因为python中class为关键字

soup.findall() # 找到 所有的 对应的标签,返回一个列表(也可做属性定位)

select

select('某种选择器(id,class,标签...选择器)'), 返回的是一个列表

  • 标签名: 不加任何修饰,类名: 前加 . ,id名: 前加 #

soup.select('.tang > ul > li > a') # . 表示, > 表示的是一个层级

soup.select('.tang > ul a'), 空格 表示的多个层级

ul 与 a中的 空格代表 多个层级
(ul下的直接层级为li,li下的直接层级为a,所有ul与a想个多个层级)

在这里插入图片描述

  • 上面的代码得到的结果为:得到类名为 tang 下的 ul 标签 下的 所有的 a 标签
对象的使用 — 获取标签中的内容

soup.tagName.text/ string/ get_text()

  1. text/get_text():可以获取某一个标签中所有的文本内容,即使不属于该标签的直系的文本内容
  2. string:只可以获取该标签下面直系的文本内容
对象的使用 — 获取标签中的属性值

soup.tagName['属性名']

解释:
在这里插入图片描述

  1. 对应标签div,其下没有直系文本内容,所以使用string为空;
    但使用 .text或.get_text()可以得到所有的文本内容(清明… 秦时… 岐王…等等)
  2. 对应标签a,其下有属性href,可以使用soup.a['href']获取对应的值;
    其下面的文本称为直系文本内容

法3 xpath — 仅可找标签相关内容

原理:
  1. 实例化一个etree的对象,且需要将被解析的页面源码数据加载到该对象中。
  2. 调用etree对象中的xpath方法结合着xpath表达式实现标签的定位和内容的捕获。
实例化etree对象
# 导入模块
from lxml import etree

tree = etree.parse(filePath)  # 将本地的页面加载到该对象,filePath为路径

page_text = response.text
tree = etree.HTML(page_text)  # 将页面上的数据加载到该对象 
使用etree对象的xpath函数进行提取

xpath('xpath表达式')

  • 返回的是一个 列表 ,其中每个元素为定位到的标签所抽象的 对象

  • xpath表达式是根据层级关系进行标签的定位

  • xpath表达式

    • 表达式中不要有空格
    • xpath表达式中不可以出现tbody标签
    • /: 1.表示的是从根节点开始定位(在表达式开头时)。2.表示的是一个层级
      r = tree.xpath('/html/body/div') — 先从根结点找到html标签,再一个层级一个层级找body,div标签
    • //: 1.表示从任意位置开始定位(在表达式开头时)。2.可以表示的是多个层级
      r = tree.xpath('/html//div') — 先找到html标签,可以跨多个层级找到div这个标签
    • xpath表达式中不可以出现tbody标签,如果出现,使用 **//**来替换 “/tbody/”
  • 使用方式

    • 属性定位

      • tag[@attrName="attrValue"] 注意:属性名都在@后面
        例如:div[@class='song'] 找到class属性值为song的div标签
        bs4中class属性为class_,xpath则为@class
    • 索引定位

      • //div[@class="song"]/p[3] 索引是从1开始的要得到第几个标签,直接使用索引
        在这里插入图片描述
        得到任意位置的class属性值为song的div标签下的 第三个p标签
    • 取文本

      • /text() 获取的是标签中直系的文本内容(因为使用了一个 / ,代表一个层级)
      • //text() 标签中非直系的文本内容(所有的文本内容)(因为使用 // ,代表多个层级)
      • 返回的是一个列表
    • 取属性

      • 先定位到对应的标签,之后在xpath表达式中加入/@attrName得到属性名
        属性名在@后
例子
#对下述url发起请求解析出视频详情页的url和视频的名称
headers = {
    'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'
}
url = 'https://www.pearvideo.com/category_5'
page_text = requests.get(url=url,headers=headers).text

# 实例化对象
tree = etree.HTML(page_text)  
# 获得所有li标签
li_list = tree.xpath('//ul[@id="listvideoListUl"]/li') 

# 通过循环找到每个li标签下的a标签中的href属性
for li in li_list:
    detail_url = li.xpath('./div/a/@href')[0]
  • tree.xpath('//ul[@id="listvideoListUl"]/li')得到对应的所有li标签,返回的是一个列表
  • li.xpath('./div/a/@href')中的“./”代表相对于li标签而言的

post模拟登录 — cookie 与 requests.Session()

在这里插入图片描述

  • preserve log :保留请求日志,跳转页面的时候勾选上,可以看到跳转前的请求,也可适用于chrome开发者工具抓包的问题

    可以看出登陆时发送的相关请求

  • login所对应的数据包,可以发现我们需要进行post请求,并可以得知登录后的页面对应的detail_url,携带相关用户名,密码,验证码(需要对登录界面的验证码进行识别)等进行登录

    • 将知道的参数放到post请求的参数中,代码:response = requests.post(url=login_url,headers=headers,data=data)

      [data为相关的参数]

    • 判断是否登录成功,代码response.status_code200为成功

  • 注意:为了登录所发送的url:login_url

    要发从登录的post请求,不是对登录界面那个url进行发送post请求

    而是要对要 点击登录那个按钮 所发送的那个请求包中对应的url进行发送post请求(上图中对应的 login?1=… 数据包)

登录成功后,要相对成功登录的页面进行之后的操作,需要使用cookie进行相关登录状态的申明

  • cookie值的来源是哪里?
    模拟登录post请求后,由服务器端创建。

  • session会话对象:
    作用:
    1.可以进行请求的发送。
    2.如果请求过程中产生了cookie,则该cookie会被自动存储/携带在该session对象中。

  • 过程:

    创建一个session对象: session = requests.Session()
    使用session对象进行模拟登录post请求的发送(cookie就会被存储在session中)
    session对象对个人主页对应的get请求进行发送(携带了cookie)

实例:

import requests
from lxml import etree

headers = {
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36'
}

# post请求的发送(模拟登录)
login_url = 'http://www.renren.com/ajaxLogin/login?1=1&uniqueTimestamp=2019431046983'
data = {
	# 登录需要的信息,可以从页面中的请求中获取
}

# 重点:  使用session进行post请求的发送
response = session.post(url=login_url,headers=headers,data=data)  # 此时模拟登录,session中保存cookie的信息

# 重点:  使用携带cookie的session进行get请求的发送
# 注意是对登录后的页面进行请求,该detail_url任在登录后发送的请求中得到
detail_page_text = session.get(url=detail_url,headers=headers).text 

with open('bobo.html','w',encoding='utf-8') as fp:
    fp.write(detail_page_text)

代理代理:— 破解封IP这种反爬机制。

代理的作用:

  • 突破自身IP访问的限制
  • 隐藏自身真实IP

代理相关的网站:

代理ip的类型:

  • http:应用到http协议对应的url中
  • https:应用到https协议对应的url中

代理ip的匿名度:

  • 透明:服务器知道该次请求使用了代理,也知道请求对应的真实ip
  • 匿名:知道使用了代理,不知道真实ip
  • 高匿:不知道使用了代理,更不知道真实的ip

实例:

import requests

# proxies参数中 键为 代理的类型, 值为 代理的ip
page_text = requests.get(url=url,headers=headers,proxies={"https":'222.110.147.50:3128'}).text

异步爬虫的方式

1.多线程,多进程(不建议)

    好处:可以为相关阻塞的操作单独开启线程或者进程,阻塞操作就可以异步执行。
    弊端:无法无限制的开启多线程或者多进程。

2.线程池、进程池(适当的使用)

    好处:我们可以降低系统对进程或者线程创建和销毁的一个频率,从而很好的降低系统的开销。
    弊端:池中线程或进程的数量是有上限。

from multiprocessing.dummy import Pool

pool = Pool(num) # num 表示线程池中线程数 实例化一个pool对象

pool.map(fun, iterable) # fun是函数,定了阻塞的操作,iterable表示可迭代对象,表示对什么进行操作

  • 将iterable中的每个对象进行fun中定义的操作
  • 返回的是一个列表,是fun对每个迭代对象的操作返回值
  • 注意:这里的进程池pool对象定义一定放在main函数下,否则会出现如下报错
 RuntimeError: 
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...
 The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.

例子:

import time
# 导入线程池模块对应的类
from multiprocessing.dummy import Pool

# 使用线程池方式执行
start_time = time.time()

def get_page(str):
    print("正在下载 :", str)
    time.sleep(2)
    print('下载成功:', str)

name_list = ['xiaozi', 'aa', 'bb', 'cc']

# 实例化一个线程池对象
pool = Pool(4)
# 将列表中每一个列表元素传递给get_page进行处理。
pool.map(get_page, name_list)

pool.close()  # 关闭线程池
pool.join()  # 当子进程退出时父进程再退出
end_time = time.time()
print(end_time - start_time)
  • get_page 类似于爬虫中对很多url的操作(下载视频url之类的)
  • name_list 类似于爬虫中url,是一个可迭代对象(例如:列表)

3.单线程+异步协程(推荐)

博客:Python异步编程【asyncio】【异步编程】【协程】

  1. event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上,
    当满足某些条件的时候,函数就会被循环执行。
  2. coroutine:协程对象,我们可以将协程对象注册到事件循环中,它会被事件循环调用。
    我们可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回一个协程对象。
  3. task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。
  4. future:代表将来执行或还没有执行的任务,实际上和 task 没有本质区别。
  5. async 定义一个协程.
  6. await 用来挂起阻塞方法的执行。
异步爬取数据 — aiohttp
  • 在异步协程中如果出现了同步模块相关的代码,那么就无法实现异步。

  • request.get()是基于同步的代码,所以在异步操作中出现该代码就无法实现异步操作

所以,要使用基于异步的网络请求模块 来进行对指定url进行请求 — aiohttp

使用该模块中ClientSession来实例化一个对象, 进行网络请求的发送

在异步函数中

async def get_page(url):
    async with aiohttp.ClientSession() as session: 
        # 直接使用session进行发送请求 get()、post():
        #headers 进行UA伪装, params/data 进行参数处理, proxy='http://ip:port' 设置代理 注意不是字典二十字符串
        async with await session.get(url) as response:  # 响应对象
            #text()返回字符串形式的响应数据
            #read()返回的二进制形式的响应数据
            #json()返回的就是json对象
            
            #注意:获取响应数据操作之前一定要使用await进行手动挂起
            page_text = await response.text()  # 与request模块使用不同,此时text不为属性而是方法
            print(page_text)
        
tasks = []

for url in urls:
    c = get_page(url)
    task = asyncio.ensure_future(c)  # 创建一个task对象
    tasks.append(task)

loop = asyncio.get_event_loop()  # 创建事件循环
loop.run_until_complete(asyncio.wait(tasks))  # 将对象加入事件循环中并运行
  • text()返回字符串形式的响应数据
    read()返回的二进制形式的响应数据
    json()返回的就是json对象

  • session.get()此时有一定的耗时,所以要用到await进行主动的挂起,因为:

    await是一个只能在协程函数中使用的关键字,用于遇到IO操作时挂起 当前协程(任务),当前协程(任务)挂起过程中 事件循环可以去执行其他的协程(任务),当前协程IO处理完成时,可以再次切换回来执行await之后的代码

  • 获取响应数据操作之前一定要使用await进行手动挂起

    page_text = await response.text()

selenium

基于浏览器自动化的模块

作用

  • 可以帮助我们便捷的获取动态加载的数据 — 之前是对Ajax进行请求
  • 便捷的进行模拟登录 — 之前是要找到 点击登录按钮后对应的数据包中的url 发送post请求,请求中带着相关的参数

selenium使用流程

使用

  1. 实例化浏览器对象

from selenium import webdriver

bro = webdriver.Chrome(executable_path='路径') # 实例化浏览器对象

  • 里面传入驱动程序路径,但代码在执行的时候会自行去寻找chromedriver.exe(在对应python目录下寻找),不再需要指定chromedriver.exe路径

  • 当实例化浏览器对象后,运行程序可以打开浏览器

  1. 让浏览器发起一个url对应请求 — get(url)

bro.get('网址')

  1. 获取浏览器当前页面源码数据 — page_source

bro.page_source

  • 可以得到页面中动态加载的数据,如果不用selenium,需要找到对应的Ajax请求(从Network中XHR找),然后对其进行解析
  1. 关闭浏览器对象

bro.quit()

  1. 标签定位 — find系列函数

在这里插入图片描述

得到搜索框对应的标签,从开发者模式中看到对应的为input标签,其id的值为q

search_input = bro.find_element_by_id('q')

  1. 标签交互 — .send_keys() .click()
  • 输入到收索框search_input.send_keys('搜索的词')

  • 点击.click()

  1. 执行js代码

bro.execute_script('代码')

  1. 其他操作
  • 浏览器回退.back()
  • 浏览器前进.forward()

selenium处理iframe

iframe用于在页面中嵌套子页面

  • 如果想要的标签在iframe中,直接使用标签定位find系列函数是不能得到对应标签的
  • 找到页面中的标签后一定要检查是否在iframe中

操作:

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

动作链

from selenium.webdriver import ActionChains
# 实例化动作链对象
action = ActionChains(bro)
# 点击长按指定的标签
action.click_and_hold(div)

for i in range(5):
    # move_by_offset(x,y): 移动x水平方向 y竖直方向 表示长按且点击的滑动
    # perform()立即执行动作链操作
    action.move_by_offset(17,0).perform()
    sleep(0.5)

#释放动作链
action.release()
  • move_by_offset(x,y): 移动x水平方向 y竖直方向 表示长按且点击的滑动

selenium处理iframe

  • 如果定位的标签存在于iframe标签之中,则必须使用switch_to.frame(id)

动作链(拖动):from selenium.webdriver import ActionChains

  • 实例化一个动作链对象:action = ActionChains(bro)

  • click_and_hold(div):长按且点击操作

  • move_by_offset(x,y)

  • .perform()让动作链立即执行

  • .release()释放动作链对象

动作链与iframe实例

from selenium import webdriver
from time import sleep
from selenium.webdriver import ActionChains

bro = webdriver.Chrome()

bro.get('https://qzone.qq.com/')

bro.switch_to.frame('login_frame')

a_tag = bro.find_element_by_id("switcher_plogin")
a_tag.click()


userName_tag = bro.find_element_by_id('u')
password_tag = bro.find_element_by_id('p')
userName_tag.send_keys('328410948')
password_tag.send_keys('123456789')
btn = bro.find_element_by_id('login_button')
btn.click()

# 对滑动验证进行操作
sleep(5)  # 等待滑动验证界面加载出来,否则会报错
bro.switch_to.frame("tcaptcha_iframe")  # 找到对应的滑块所在的iframe
smooth = bro.find_element_by_id('tcaptcha_drag_thumb')
# 实例化动作链对象
action = ActionChains(bro)
# 点击长按指定的标签
action.click_and_hold(smooth)

action.move_by_offset(170, 0).perform()
action.release()
bro.close()

在这里插入图片描述

无头浏览器(实现无可视化界面)

看不到打开浏览器等一系列可视化的操作

# 实现无可视化界面
from selenium.webdriver.chrome.options import Options

# 实现无可视化界面的操作
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')

# 实现无可视化
bro = webdriver.Chrome(executable_path='驱动程序路径', chrome_options=chrome_options)
  • 参数chrome_options会出现DeprecationWarning: use options instead of chrome_options bro = webdriver.Chrome(chrome_options=chrome_options)说明可以使用options参数代替chrome_options

重:实现让selenium规避被检测的风险

# 实现规避检测
from selenium.webdriver import ChromeOptions

# 实现规避检测
option = ChromeOptions()
option.add_experimental_option('excludeSwitches', ['enable-automation'])

# 如何实现让selenium规避被检测到的风险
bro = webdriver.Chrome(executable_path='./chromedriver', options=option)
  • 当同时使用无头浏览器和规避检测时,只能一个使用chrome_options参数,一个用options

12306模拟实例 — 破解验证码

问题:12306特殊的验证码:

验证码

解决:超级鹰http://www.chaojiying.com/about.html

  • 登录:普通用户
  • 题分查询:充值
  • 创建一个软件(id)
  • 识别类型为:坐标选择计算
  • 下载示例代码 修改相关代码可以得到对应的在验证码中的坐标值

问题:如果对其验证码url进行下载,再使用超级鹰识别,会发现是错误的

  • 因为12306为了反爬,其验证码url是动态的,即每次访问会不一样,也就是说下载下来的验证码和网站上的验证码不一样

解决:可以使用进行全部截屏 并 对改截屏页面进行裁剪

  • bro.save_screenshot('保存截屏的名字')
  • 进行裁剪 — 需要知道验证码所在的区域
    • 先定位到对应的验证码标签,再使用函数进行得到该标签对应的坐标
# 确定验证码图片对应的左上角和右下角的坐标(裁剪的区域就确定)      验证码的xpath可以在开发者工具中得到
code_img_ele = bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img')

location = code_img_ele.location  # 验证码图片左上角的坐标 x,y
print('location:',location)
size = code_img_ele.size  # 验证码标签对应的长和宽
print('size:',size)

# 左上角和右下角坐标
rangle = (
    int(location['x']), int(location['y']), int(location['x'] + size['width']), int(location['y'] +	size['height']))
# 进行裁剪
from PIL import Image

i = Image.open('./aa.png')
# crop 根据指定区域进行图片裁剪
frame = i.crop(rangle)
frame.save('./code.png')
  • 得到验证码截屏后就是用超级鹰识别得到坐标

    超级鹰返回的坐标是相对于验证码小窗口,而我们点击是要基于整个页面的,

    • 所以可以将selenium作用域限制到验证码中

问题:得到坐标后如何点击

解决:使用selenium

# 确定验证码图片所在的标签
code_img_ele = bro.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img')

#遍历列表,使用动作链对每一个列表元素对应的x,y指定的位置进行点击操作
for l in all_list:  # all_list [[x1,y1],[x2,y2]] 
    x = l[0]
    y = l[1]  
    ActionChains(bro).move_to_element_with_offset(code_img_ele, x, y).click().perform()
    time.sleep(0.5)
  • .move_to_element_with_offset(标签, x, y 相对于该标签的x,y坐标
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值