Osheep

时光不回头,当下最重要。

python爬虫入门 实战(五)---用webdriver实现微博“搬家”(1)


《python爬虫入门 实战(五)---用webdriver实现微博“搬家”(1)》

效果展示动图

需求:
博主之前有一段时间突然不想玩微博了,然后正好表弟想玩,就给他用了,手机绑定也换成了他的号码。近期突然又想要玩,就重新开了个号。新号微博空空的,也没有什么关注。于是就产生了两个需求,正好可以借这个机会学习一下自动化测试工具webdriver的基本使用:
1、将原微博的博文搬到新账号
2、用新账号关注原微博的所有关注

涉及:
1、request的基本使用
2、json解析
3、用request下载图片并保存到本地
4、用webdriver登录并发送微博
5、用autoIT解决非input型的文件上传

本篇目录:
1.思路
2.爬取原微博
3.用webdriver启动浏览器登录微博
4.用webdriver发布带图片的微博
5.完整代码
6.效果展示动图
7.参考


《python爬虫入门 实战(五)---用webdriver实现微博“搬家”(1)》

图1.封面而已

思路

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

《python爬虫入门 实战(五)---用webdriver实现微博“搬家”(1)》

图2.json结构

之前教程里也说了,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方法找到账号和密码输入框,键入账号密码进行登录,有时可能需要验证。以下展示的是账号输入框的位置与属性:

《python爬虫入门 实战(五)---用webdriver实现微博“搬家”(1)》

图3.账号输入框位置

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弹窗进行一波骚操作了。

《python爬虫入门 实战(五)---用webdriver实现微博“搬家”(1)》

图4.图片上传按钮

从上图看,可以发现这个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爬虫入门 实战(四)—爬“榜姐”话题微博及热门评论末尾

效果展示动图

《python爬虫入门 实战(五)---用webdriver实现微博“搬家”(1)》

效果展示动图

参考

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 实现上传文件

点赞