需求:
博主之前有一段时间突然不想玩微博了,然后正好表弟想玩,就给他用了,手机绑定也换成了他的号码。近期突然又想要玩,就重新开了个号。新号微博空空的,也没有什么关注。于是就产生了两个需求,正好可以借这个机会学习一下自动化测试工具webdriver的基本使用:
1、将原微博的博文搬到新账号
2、用新账号关注原微博的所有关注
说明:
本篇主要叙述第一个需求的实现,第二个需求的实现参见python爬虫入门 实战(六)---用webdriver实现微博批量自动关注。
涉及:
1、request的基本使用
2、json解析
3、用request下载图片并保存到本地
4、用webdriver登录并发送微博
5、用autoIT解决非input型的文件上传
本篇目录:
1.思路
2.爬取原微博
3.用webdriver启动浏览器登录微博
4.用webdriver发布带图片的微博
5.完整代码
6.效果展示动图
7.参考
思路
1、爬取原微博的博文内容,爬一条发一条
2、文字信息保存在list里,图片保存到本地
3、用webdriver实现批量发送微博
4、上传图片借助第三方工具autoIT
注:
为什么用webdriver来发微博而不用微博开放平台的api?
因为发布带图片的微博的api是高级api需要创建应用并通过审核才能申请,而过审并不是可以糊弄的,需要成熟且系统的应用。
爬取原微博
关于爬取微博的详细思路和实现可以参考我之前写的另一篇:
python爬虫入门 实战(四)---爬“榜姐”话题微博及热门评论
这里会直接引入实战四的代码模块crawl_weibo,其中的getWeibo方法可以获取到指定微博用户的所有微博的json,并提取其中的cards(一个list数组,所有的微博信息都在里边)返回,该模块详细源码参见以上链接末尾。
getWeibo方法:
# 获取指定博主的所有微博card的list
def getWeibo(self,id,page):#id(字符串类型):博主的用户id,page(整型):微博翻页参数
url='https://m.weibo.cn/api/container/getIndex?type=uid&value='+id+'&containerid=107603'+id+'&page='+str(page)
response=requests.get(url)
ob_json=json.loads(response.text)
list_cards=ob_json['cards']
return list_cards# 返回本页所有的cards
首先明确我们要爬取原微博的所有除了转发以外的微博的原文、图片以及来源、点赞数、评论数等其他信息。接下来先分析一下json 的结构,json的格式化推荐json.cn。
之前教程里也说了,json就是list和dict的互相嵌套,所以只要会用list和dict,搞清楚json结构,提取数据就很容易了。
我们把文字信息提取出来,把图片都下载到本地的一个文件夹里,以便之后发微博上传。对于图片存储,用request来get一下图片的url,获取的返回内容用open创建一个.jpg格式的file来写入。为了上传的时候方便填写图片地址,图片按顺序编号存入项目下的img文件夹中。上传时只需要根据图片总数按序遍历即可
获取微博内容数据代码:
def getContent(self,card):# 获取该card下微博的内容
mblog=card['mblog']
count_img=0# 每条微博重新计数图片数,用于发送图片的地址
text= html.fromstring(mblog['text'])
text=text.xpath('string(.)')#过滤正文中的多余标签
url_img=''
if 'pics' in mblog:#如果mblog中有键'pics',说明该条微博有图片,存储图片到本地
pics=mblog['pics']
for pic in pics:
url_img=pic['large']['url']
ir = requests.get(url_img)
if ir.status_code == 200:# 如果请求已成功
count_img+=1# 给图片计数
open('../img/'+str(count_img)+'.jpg', 'wb').write(ir.content)# 保存在img文件夹下,发送也从这个文件夹下找
scheme=card['scheme']# 原微博链接
created_at=mblog['created_at']# 原微博创建时间
source=mblog['source']# 原微博来源,即终端
reposts_count=mblog['reposts_count']# 原微博转发数
comments_count=mblog['comments_count']# 原微博评论数
attitudes_count=mblog['attitudes_count']# 原微博点赞数
return text,count_img,scheme,created_at,source,reposts_count,comments_count,attitudes_count# 其实返回的是一个list
将getWeibo返回的list(也就是json中的cards)中的一个card传入以上函数,即可返回我们需要的数据,细节见注释。通过这个方法我们需要发布的微博数据已经可以爬取到了,接下来我们要登录微博把它们发布出去。
用webdriver启动浏览器登录微博
webdriver的使用其实非常简单。可以这样描述:
1、用驱动器启动浏览器并打开一个连接
2、根据页面html源码的结构找到指定的元素(按钮、文本框等)
3、对目标元素采取目标动作(点击、输入等)
使用webdriver需要下载selenium模块和浏览器驱动。
1、下载安装selenium模块,命令行:
pip install selenium
2、博主用的是chrome浏览器,所以本篇以其对应的驱动chromedriver.exe为例,百度或者google就可以找到下载链接,下载好了解压到任意路径即可,代码中指定该路径。
3、一定要保证selenium模块、浏览器驱动、浏览器都是最新版本,以免产生不必要的bug。一般pip指令安装的都是最新版本的模块。
做好以上准备之后,就可以在代码里引入webdriver了,我们先用登录来打个样儿。代码要做的就是驱动启动浏览器后,get微博的主页,用driver的各种find_element方法找到账号和密码输入框,键入账号密码进行登录,有时可能需要验证。以下展示的是账号输入框的位置与属性:
webdriver的基本使用:
1、可以根据class和id来找到元素,也可以用xpath语法和css选择器来找到元素
driver.find_element_by_id('id')
driver.find_element_by_class_name('classname')
driver.find_element_by_css_selector('input[type="password"]')
driver.find_element_by_xpath('//div[@node-type="textElDiv"]/textarea[@class="W_input"]')
2、用element.click点击元素,用element.send_keys向文本框键入文字,其他动作可以用ActionChains,参考selenium之 玩转鼠标键盘操作(ActionChains)。
3、详细说明可参考Python爬虫利器五之Selenium的用法,更多用法参考网络教程和文档。
登录代码:
driver = webdriver.Chrome(executable_path='../drivers/chromedriver.exe')# chrome浏览器驱动
url='http://weibo.com/'
driver.get(url)# get打开微博主页
input_account=driver.find_element_by_id('loginname')# 找到账号输入框
input_psw=driver.find_element_by_css_selector('input[type="password"]')# 找到密码输入框
#输入账号密码并登录
input_account.send_keys('account')
input_psw.send_keys('password')
bt_logoin=driver.find_element_by_class_name('login_btn')# 找到登录按钮
bt_logoin.click()# 点击登录
不过以上代码在网络状态不好的时候会产生找不到元素的bug,这主要是因为页面还没有加载完毕造成的。
应该将find账号输入框的代码修改为:
# 参数10为超时时间,即最大等待时间为10秒,关于lambda就自行百度吧
bt_logoin=WebDriverWait(driver,10).until(lambda x:x.find_element_by_id('loginname'))
关于等待页面加载的其他方法的具体介绍,可以参考Python selenium 三种等待方式详解和WebDriverWait等设置等待时间和超时时间这两篇。
出现验证码的情况:
在点击了登录按钮以后,有时会出现验证码。对于这种情况,我们只需要加入一个判断,如果点击登录后出现了验证码输入框,就断定需要输入验证码,我们在控制台手动输入并send_keys到验证码输入框即可。(验证码自动破解比较复杂,不建议在入门阶段研究)
处理验证码的代码:
# 根据验证框的存在与否判断是否要输入验证码
def isVerifyCodeExist(self):
try:# 如果成功找到验证码输入框返回true
self.driver.find_element_by_css_selector('input[name="verifycode"]')
return True
except:# 如果异常返回false
return False
# 输入验证码部分,如果无需输入则直接返回,否则手动输入成功后返回
def inputVerifyCode(self):
input_verifycode=self.driver.find_element_by_css_selector('input[name="verifycode"]')# 验证码输入框
bt_change=self.driver.find_element_by_css_selector('img[action-type="btn_change_verifycode"]')# 验证码图片,点击切换
bt_logoin=self.driver.find_element_by_class_name('login_btn')# 登录按钮
while self.isVerifyCodeExist():# 如果验证码输入框一直存在,则一直循环
print u'请输入验证码……(输入"c"切换验证码图片)'
verifycode=raw_input()
if verifycode=='c':
bt_change.click()
else:
input_verifycode.send_keys(verifycode)
bt_logoin.click()
# 点击完登录以后判断是否成功
if self.driver.current_url.split('/')[-1]=='home':# 如果连接已经跳转到home
print u'登录成功'
break
else:
print u'输入的验证码不正确'
在之前的登录代码末尾调用inputVerifyCode方法即可。
用webdriver发布带图片的微博
如果仅发布文字内容的微博,比较简单,思路与以上同理,先找到文本框元素,用send_keys输入内容,找到'发布'按钮,点击按钮即可发布。
# 上传文字
def upload_txt(self,text):
# 用xpath语法找到输入框
input_w=self.driver.find_element_by_xpath('//div[@node-type="textElDiv"]/textarea[@class="W_input"]')
input_w.send_keys(text)
# 发布
def send(self):
# 找到发布按钮并点击
self.driver.find_element_by_class_name('W_btn_a').click()
而对于需要发布图片的博文,我们还需要点击图片->点击单图/多图。接着我们可以发现目标对象不是input型的文件上传按钮,input型的文件上传我们可以用send_keys直接键入文件地址,方便快捷。但是微博这个文件上传是object型的,这只能借助第三方工具autoIT来对os弹窗进行一波骚操作了。
从上图看,可以发现这个object的id包括'swf_upbtn'这样一个字段,之后很明显是一个随时可能变化的数字串,所以我们可以用xpath的contains函数来找到它,点击这个按钮以后会弹出上传文件的窗口,这个窗口就不是selenium能控制的了,需要编写autoIT脚本来控制文件的上传操作,再在python代码中通过os模块调用这个脚本。
autoIT的安装和使用
参考selenium2 python自动化测试之利用AutoIt工具实现本地文件上传和 selenium借用AutoIt 实现上传文件这两篇。
我的autoIT脚本代码:
WinWait("打开","",10000);
ControlFocus("打开", "", "Edit1");
ControlSetText("打开" ,"", "Edit1", $CmdLine[1]);
Sleep(1000)
ControlClick("打开", "","Button1");
上述代码进行的操作是:
1、等待文件上传窗口的出现,最多等待10s(因为chrome浏览器的文件上传窗口标题是"打开",第一个参数是窗口标题)
2、将光标聚焦于文件地址编辑框
3、在编辑框里键入参数(也就是我们之后会传入的地址)
4、点击"打开"按钮
详细用法和说明请参阅以上两篇。
$CmdLine[1]可以读取到命令行的第一个参数,通过这个参数我们就可以把文件地址传入。关于参数的说明和其他的实现文件上传的方法参考Python Selenium —— 文件上传、下载,其实很简单。
上传文字和图片的方法封装如下:
# 上传文字
def upload_txt(self,text):
input_w=self.driver.find_element_by_xpath('//div[@node-type="textElDiv"]/textarea[@class="W_input"]')
input_w.send_keys(text)
sleep(1)
#运行上传图片脚步
def upload_img_script(self,time_bef,time_after,path):# path参数需要前后带双引号
sleep(time_bef)# 等待弹窗时间
os.system('C:/Users/15850/Documents/GitHub/MyWorkspace/py_study/script/upload.exe '+path)
sleep(time_after)# 等待图片加载时间
# 上传文字和单图
def upload_txt_img(self,text,img_path):
self.upload_txt(text)# 将文字上传
img=self.driver.find_element_by_css_selector('a[action-type="multiimage"]')# 图片按钮
img.click()# 点击图片按钮
sleep(1)# 等待加载其他按钮
#单图/多图按钮,即上传图片按钮
bt_uploadimg=WebDriverWait(self.driver,10).until(lambda x:x.find_element_by_xpath('//object[contains(@id,"swf_upbtn")]'))
bt_uploadimg.click()# 点击上传按钮
self.upload_img_script(1,2,img_path)
# 上传文字和多图
def upload_txt_multiImg(self,text,img_path_list):
self.upload_txt_img(text,img_path_list[0])# 将文字和第一张图片上传
len_imgs=len(img_path_list)# 图片地址list的长度
bt_uploadimg=WebDriverWait(self.driver,10).until(lambda x:x.find_element_by_xpath('//li[@node-type="uploadBtn"]/div/object[contains(@id,"swf_upbtn")]'))
for i in range(len_imgs-1):# 将剩余图片上传
bt_uploadimg.click()
self.upload_img_script(1, 2,img_path_list[i+1])
其中这一行即是调用了编译成exe文件的上传文件的脚步,参数字符串先指明脚本路径,空一格加上path这个参数。
os.system('C:/Users/15850/Documents/GitHub/MyWorkspace/py_study/script/upload.exe '+path)
注意path传入的字符串必须带双引号,字符串本身也需要单引号包括,且分隔符必须为"\",如下:
'"C:\\Users\\15850\\Documents\\GitHub\\MyWorkspace\\py_study\\img\\1.jpg"'
完整代码
(源码已上传github:python爬虫入门实战)
1、主要操作步骤写在weibo_transport模块中
2、引入crawl_weibo模块用于爬取原微博数据
3、引入upload_driver模块用于将数据发布到新账号上
4、3个模块分别位于github项目中的crawl与weibo文件夹下
weibo_transport模块
# -*- coding:utf-8 -*-
'''
Created on 2017年6月25日
@author: wycheng
'''
from crawl import crawl_weibo
import upload_driver
from lxml import html
import requests
def getContent(card):# 获取该card下微博的内容
mblog=card['mblog']
count_img=0# 每条微博重新计数图片数,用于发送图片的地址
text= html.fromstring(mblog['text'])
text=text.xpath('string(.)')#过滤正文中的多余标签
url_img=''
if 'pics' in mblog:#有图片的存储图片
pics=mblog['pics']
for pic in pics:
url_img=pic['large']['url']
ir = requests.get(url_img)
if ir.status_code == 200:# 如果请求已成功
count_img+=1# 给图片计数
open('../img/'+str(count_img)+'.jpg', 'wb').write(ir.content)# 保存在img文件夹下,发送也从这个文件夹下找
print u'图片:'+url_img
scheme=card['scheme']
created_at=mblog['created_at']
source=mblog['source']
reposts_count=mblog['reposts_count']
comments_count=mblog['comments_count']
attitudes_count=mblog['attitudes_count']
return text,count_img,scheme,created_at,source,reposts_count,comments_count,attitudes_count# 其实返回的是一个list
#登录
uploader=upload_driver.Uploader()
uploader.login('account', 'password')# 填写你的账号密码
crawl_weibo=crawl_weibo.CrawlWeibo()
# 先爬取第34页进行发布,作为范例
# 这里的页码不是微博客户端上的页码,这个接口返回的一页只有10个card
# 博主原微博有370+条微博,所以一共有38页
cards=crawl_weibo.getWeibo('2622535523',34)# 第一个参数是用户id,第二个是页数
len_cards=len(cards)
for j in range(len_cards):
index=len_cards-1-j
card=cards[index]
# (微博的cardtype==9,实战四也有说明,转发的微博mblog里有‘retweeted_status’这个key)
if card['card_type']==9 and not 'retweeted_status' in card['mblog']:# 如果这是一条非转发的微博的card
# 获取这条微博的各个数据
content_list=getContent(card)
text,count_img,scheme,created_at,source,reposts_count,comments_count,attitudes_count=content_list
# 发送这条微博的内容到新账号
# 拼接要发送的文本
text=created_at+u' 来自 '+source+'\n'+text+' \n'+u'转发 '+str(reposts_count)+u' 评论 '+str(comments_count)+u' 点赞 '+str(attitudes_count)
path_list=[]# 图片地址list
for i in range(count_img):# 根据图片总数量生成所有图片地址
path_list.append('C:\\Users\\15850\\Documents\\GitHub\\MyWorkspace\\py_study\\img\\'+str(i+1)+'.jpg')
# 上传内容
if count_img==0:# 没有图片
uploader.upload_txt(text)
elif count_img==1:# 单张图片
uploader.upload_txt_img(text, path_list[0])
else:# 多张图片
uploader.upload_txt_multiImg(text,path_list)
uploader.send()
upload_driver模块
# -*- coding:utf-8 -*-
'''
Created on 2017年6月25日
@author: wycheng
'''
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from time import sleep
import os
class Uploader:
driver = webdriver.Chrome(executable_path='../drivers/chromedriver.exe')# chrome浏览器驱动
# 根据验证框的存在与否判断是否要输入验证码
def isVerifyCodeExist(self):
try:# 如果成功找到验证码输入框返回true
self.driver.find_element_by_css_selector('input[name="verifycode"]')
return True
except:# 如果异常返回false
return False
# 输入验证码部分,如果无需输入则直接返回,否则手动输入成功后返回
def inputVerifyCode(self):
input_verifycode=self.driver.find_element_by_css_selector('input[name="verifycode"]')# 验证码输入框
bt_change=self.driver.find_element_by_css_selector('img[action-type="btn_change_verifycode"]')# 验证码图片,点击切换
bt_logoin=self.driver.find_element_by_class_name('login_btn')# 登录按钮
while self.isVerifyCodeExist():
print u'请输入验证码……(输入"c"切换验证码图片)'
verifycode=raw_input()
if verifycode=='c':
bt_change.click()
else:
input_verifycode.send_keys(verifycode)
bt_logoin.click()
# 点击完登录以后判断是否成功
if self.driver.current_url.split('/')[-1]=='home':
print u'登录成功'
break
else:
print u'输入的验证码不正确'
#打开微博首页进行登录的过程
def login(self,account,password):
self.driver.implicitly_wait(10)# 设置隐性等待时间,等待页面加载完成才会进行下一步,最多等待10秒
url='http://weibo.com/'
self.driver.get(url)
#输入账号密码并登录
WebDriverWait(self.driver,10).until(lambda x:x.find_element_by_id('loginname')).send_keys(account)
self.driver.find_element_by_css_selector('input[type="password"]').send_keys(password)
bt_logoin=self.driver.find_element_by_class_name('login_btn')
bt_logoin.click()
#如果存在验证码,则进入手动输入验证码过程
if self.isVerifyCodeExist():
self.inputVerifyCode()
# 上传文字
def upload_txt(self,text):
input_w=self.driver.find_element_by_xpath('//div[@node-type="textElDiv"]/textarea[@class="W_input"]')
input_w.send_keys(text)
sleep(1)
#运行上传图片脚步
def upload_img_script(self,time_bef,time_after,path):# path参数需要前后带双引号
sleep(time_bef)# 等待弹窗时间
os.system('C:/Users/15850/Documents/GitHub/MyWorkspace/py_study/script/upload.exe '+path)
sleep(time_after)# 等待图片加载时间
# 上传文字和单图
def upload_txt_img(self,text,img_path):
self.upload_txt(text)# 将文字上传
img=self.driver.find_element_by_css_selector('a[action-type="multiimage"]')# 图片按钮
img.click()# 点击图片按钮
sleep(1)# 等待加载其他按钮
#单图/多图按钮,即上传图片按钮
bt_uploadimg=WebDriverWait(self.driver,10).until(lambda x:x.find_element_by_xpath('//object[contains(@id,"swf_upbtn")]'))
bt_uploadimg.click()# 点击上传按钮
self.upload_img_script(1,2,img_path)
# 上传文字和多图
def upload_txt_multiImg(self,text,img_path_list):
self.upload_txt_img(text,img_path_list[0])# 将文字和第一张图片上传
len_imgs=len(img_path_list)# 图片地址list的长度
bt_uploadimg=WebDriverWait(self.driver,10).until(lambda x:x.find_element_by_xpath('//li[@node-type="uploadBtn"]/div/object[contains(@id,"swf_upbtn")]'))
for i in range(len_imgs-1):# 将剩余图片上传
bt_uploadimg.click()
self.upload_img_script(1, 2,img_path_list[i+1])
# 发布
def send(self):
self.driver.find_element_by_class_name('W_btn_a').click()
sleep(4)# 等待发送成功字样消失
crawl_weibo模块
参考python爬虫入门 实战(四)---爬“榜姐”话题微博及热门评论末尾
效果展示动图
参考
1、python爬虫入门 实战(四)---爬“榜姐”话题微博及热门评论
2、selenium之 玩转鼠标键盘操作(ActionChains)
3、Python爬虫利器五之Selenium的用法
4、Python selenium 三种等待方式详解
5、WebDriverWait等设置等待时间和超时时间
6、selenium+python find_element_by_css_selector方法使用
7、selenium2 python自动化测试之利用AutoIt工具实现本地文件上传
8、selenium借用AutoIt 实现上传文件