python爬虫实战--selenium验证码保存+多线程多标签+自动点击+完整代码
来源:互联网 发布:厦门博思软件 编辑:程序博客网 时间:2024/06/16 12:11
任务介绍
最近刚刚注册了某个网站,该站有新手考核任务,其中有一项是需要达到魔力值5000。在魔力值获取方式中,我们看到这一项:“说谢谢 = 0.5个魔力值”,而网站存活种子数量达到16000+,也就意味着对每个种子说一下谢谢,轻松达到8000+的魔力值,于是,这个项目应运而生。
初步实现思路:
获取种子的页面,在每个页面中找到说谢谢的按钮,并点击后,关闭。依次进行下去即可。
相似任务:
实现对某论坛的自动回复,实现自动获取所有帖子的信息等等相关操作,无论是否需要模拟登录、模拟鼠标操作还是直接解析网站元素。
具体网站在程序中就不给出了,相信看博客的人一定都带着自己想要爬取的网站,因此,只需要对程序稍加修改即可。
selenium 牛刀小试
首先导入相关的库:
import selenium.webdriver as webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver import ActionChainsfrom selenium.webdriver.common.keys import Keys
这是整个程序里面用到的所有内容。
其中,webdriver
是主浏览器,selenium
都是基于整个浏览器的对象;WebDriverWait、EC、By
是等待网页元素加载相关的操作;Keys
是键值,如Keys.CONTROL
,Keys.ENTER
等等,ActionChains
是用鼠标进行一系列的操作。
webdriver
可用的浏览器有:
webdriver.Firefoxwebdriver.FirefoxProfilewebdriver.Chromewebdriver.ChromeOptionswebdriver.Iewebdriver.Operawebdriver.PhantomJSwebdriver.Remotewebdriver.DesiredCapabilitieswebdriver.ActionChainswebdriver.TouchActionswebdriver.Proxy
一开始我选择的是Chrome浏览器,后来改为了Firefox火狐。Chrome浏览器在执行单个元素(如验证码)截图时有坑(下文有详细说),所以后来才用的Firefox。此外,PhantomJS是匿名浏览器,没有显式的窗口。
那么,开始写程序吧。
driver = webdriver.Firefox()login_url = "http://xxxx/login.php"login_failed_url = "http://xxxx/takelogin.php"driver.get(login_url)while self.driver.current_url == login_url or self.driver.current_url == login_failed_url: time.sleep(10)# do something
首先,实体化浏览器driver,执行driver = webdriver.Firefox()
这句的时候,就会有firefox浏览器弹出来了。当执行到driver.get(login_url)
时,浏览器转到相应的网址,后面的while语句是用来等待我们手动登录的,当我们手动登录成功后,会进入到"http://hdhome.org/index.php"
,与login_url及login_failed_url都不同。接着便可以做自己想做的事情了。
我们发现单个种子的网址是类似这样的:
single_link = "http://xxxx.xxxx/details.php?id={}&hit=1".format(i)
i可以从1到30000多。于是,我们可以这样写程序,依次对每个种子执行“说谢谢”操作:
def saythanks(link): driver.get(link) try: driver.find_element_by_xpath("//input[@id='saythanks']").click() print(link, " succeed\n") except: print(link, " not succeed\n") finally: time.sleep(1) passSTART = 1END = 30000for i in range(START, END): link = "http://xxxx.xxxx/details.php?id={}&hit=1".format(i) saythanks(link)driver.close()
其中,我们使用try
、except
、finally
语句来尝试定位到’saythanks’说谢谢的按钮元素。由于有时候加载较慢就会找不到,或者是这个种子已经被删除了,所以也导致找不到该元素。
其中定位网页元素的方法有一下几种:
# locate single element in a page:find_element_by_idfind_element_by_namefind_element_by_xpathfind_element_by_link_textfind_element_by_partial_link_textfind_element_by_tag_namefind_element_by_class_namefind_element_by_css_selector# To find multiple elements (these methods will return a list):find_elements_by_namefind_elements_by_xpathfind_elements_by_link_textfind_elements_by_partial_link_textfind_elements_by_tag_namefind_elements_by_class_namefind_elements_by_css_selector
从上面可以看出,我们也可以用find_element_by_id("saythanks")
同样可以找到说谢谢的按钮。
附上到目前为止的所有程序:
GitHub地址1
完整程序中加上了logging模块,将输出日志也导入到了文件,方面以后查阅。
改进一:使用多线程多标签
在上述模块中,可以看到,我们按照种子的顺序依次进行相应的操作。在种子数量很多的时候,会显得很慢,于是,有了这个改进:使用多线程。
我们使用multiprocessing库。
from multiprocessing import Pool
先来看一个使用该多线程库的示例程序:
import timefrom multiprocessing import Pooldef run(fn): # fn: 函数参数是数据列表的一个元素 time.sleep(1) return fn * fnif __name__ == "__main__": testFL = [1, 2, 3, 4, 5, 6] print('shunxu:') # 顺序执行(也就是串行执行,单进程) s = time.time() for fn in testFL: run(fn) e1 = time.time() print("顺序执行时间:", int(e1 - s)) print('concurrent:') # 创建多个进程,并行执行 pool = Pool(5) # 创建拥有5个进程数量的进程池 # testFL:要处理的数据列表,run:处理testFL列表中数据的函数 rl = pool.map(run, testFL) pool.close() # 关闭进程池,不再接受新的进程 pool.join() # 主进程阻塞等待子进程的退出 e2 = time.time() print("并行执行时间:", int(e2 - e1)) print(rl)
于是,模仿上述程序,我们也使用多线程来执行说谢谢。说谢谢的过程其实有两步:一是打开网页,二是对每个网页定位到每个元素并点击。
如果对一、二两个步骤都执行多线程会出错,可能是由于多窗口的原因。因此我目前只对打开网页的步骤执行了多线程的操作。
上述也提到了,要同时打开多个窗口,则需要使用浏览器的多标签功能。打开一个新的标签的程序需要执行js脚本,如下:
def open_url(url): newwindow = 'window.open("{}")'.format(url) driver.execute_script(newwindow)
于是多线程部分的改进如下:
START = 25980 END = 30000 Thread_Num = 3 t = 1 for i in range(START, END, Thread_Num): pool = Pool(Thread_Num) all_links = ["http://xxxx.xxxx/details.php?id={}&hit=1".format(i) for i in range(i, i + Thread_Num)] print(all_links) # noinspection PyBroadException try: rl = pool.map(open_url, all_links) pool.close() pool.join() except: print("multi thread start failed, next!!") logging.info("multi thread start failed, next!!") time.sleep(5) continue # 通过移动句柄来说谢谢 saythanks() # sleep more time.sleep(0.5) if t % 3 == 0: time.sleep(0.5) if t % 5 == 0: driver.switch_to.window(driver.window_handles[0]) driver.refresh() mystr = driver.find_elements_by_xpath('//span[@class="medium"]')[0].text bonus = re.search("\s[0-9,.]*\s", mystr).group() usrName = re.search("\s[a-zA-Z0-9]*\s", mystr).group() print(driver.current_url, "normal refresh,{}bonus is{}now...".format(usrName, bonus)) logging.info(driver.current_url + "normal refresh,{}bonus is{}now...".format(usrName, bonus)) time.sleep(1) t = t + 1 driver.quit() logging.info("{}: driver quit, program stop.".format( time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))))
为了不让浏览器检测到,我只是用了三个线程,可以适当的增加。saythanks()下面的部分程序是为了增加更多的延迟并且显示相应的信息。其中if t % 5 == 0:
中,我们移动到主页上,进行刷新操作,然后定位到用户信息那一栏:
bonus = re.search("\s[0-9,.]*\s", mystr).group()usrName = re.search("\s[a-zA-Z0-9]*\s", mystr).group()
这个部分使用了re正则项来找出当前的魔力值以及用户名,并显示出来。
其中,说谢谢的程序也需要对多标签进行相应的改进,程序如下:
def saythanks(): while len(driver.window_handles) > 1: driver.switch_to.window(driver.window_handles[-1]) # noinspection PyBroadException try: WebDriverWait(driver, 20).until(EC.presence_of_element_located((By.ID, "outer"))) except: driver.refresh() time.sleep(1) print(driver.current_url, " refresh ---") # noinspection PyBroadException try: driver.find_element_by_xpath("//input[@id='saythanks']").click() print(driver.current_url, " succeed") logging.info(driver.current_url + " succeed~") except: print(driver.current_url, " not succeed") logging.info(driver.current_url + " not succeed!") finally: time.sleep(1) driver.close() driver.switch_to.window(driver.window_handles[-1])
通过在不同窗口的句柄之间移动,来依次进行说谢谢的步骤。
在每个网页加载的时候,我们执行了等待的操作:
WebDriverWait(driver, 20).until(EC.presence_of_element_located((By.ID, "outer")))
一直等到最外层的元素出现。我选择的”outer”这个元素,是在无论这个种子是否存在的时候都会出现的。
driver.switch_to.window(driver.window_handles[-1])
将窗口转移到最后打开的那个窗口。
driver.close()driver.switch_to.window(driver.window_handles[-1])
关闭当前的个窗口,并转到当前的最后一个窗口。需要注意的是:窗口虽然关闭了,但是,driver依旧会停在那个已经失效的窗口,并不会自动的转到新的窗口(虽然在浏览器中看上去到了新的窗口),所以,需要我们自己手动的移动窗口的句柄。
这边还存在一个问题,就是多标签的时候,自动切换标签的时候,浏览器会自动弹出来。这样子便有点烦人,毕竟我们只是想让他在后台自己跑, 所以,我加上了一个虚拟窗口,使用的是pyvirtualdisplay
库。
以下是pyvirtualdisplay
库在ubuntu中的安装步骤:
pip install pyvirtualdisplaysudo apt install xvfbsudo apt install xserver-xephyr
下面是pyvirtualdisplay
具体的使用方式:
from pyvirtualdisplay import Displayif __name__ == "__main__": display = Display(visible=1, size=(800, 600)) display.start()
把虚拟窗口放在一开始处的位置即可。
也可以将visible改为0,浏览器就完全不可见了。
最后附上这个阶段的完整程序:
github地址2
改进二:验证码保存+面向对象编程
验证码保存:
code = self.driver.find_element_by_xpath("//img[@alt='CAPTCHA']") img = code.screenshot_as_png img_name = "./code/code{}.png".format(time.strftime('%Y-%m-%d_%H%M%S', time.localtime(time.time()))) with open(img_name, 'wb') as f: f.write(img) rec_code = self.code_recog(img_name)
其中,验证码保存步骤使用了selenium自带的元素截图功能,而不是全屏截图。这边正是我从chrome浏览器改为firefox浏览器的真实原因。chrome浏览器中的元素截图不可用!会报错!故此选用firefox浏览器。
面向对象编程就是对函数使用了类,把多个函数合并到了同一个类中去。
完整程序在最后给出。
改进三:使用pyqt获得验证码图片
思路是:从网页中解析到验证码的图片,然后下载到本地;接着使用pyqt弹出一个窗口,窗口中显示获取到的验证码,手动输入验证码后点击关闭。
简化了每次登录的流程,账号、密码记录在程序中自动输入,只需要手动输入验证码。
其中,基于pyqt5图形界面的窗口部分的程序如下:
# CodeRecognition.pyimport sysfrom PyQt5 import QtWidgets, QtGuifrom PyQt5.QtWidgets import *from PyQt5.QtGui import *class CodeRecognition(QtWidgets.QWidget): def __init__(self, parent=None): QtWidgets.QWidget.__init__(self, parent) self.setWindowTitle("请手动输入验证码") self.resize(250, 150) self.center() # 界面初始化 self.code_edit = QLineEdit() self.label_code = QtWidgets.QLabel() self.init_interface() self.img_path = './image_3.png' self.show_code_img() # 输出的识别码 self.out_code = 'To_be_recognize' def init_interface(self): label1 = QtWidgets.QLabel('请输入验证码:', self) label2 = QtWidgets.QLabel('输入完成后点击关闭按钮即可。', self) self.code_edit.setToolTip('请输入验证码') button2 = QtWidgets.QPushButton('关闭', self) grid = QGridLayout() grid.setSpacing(0) grid.addWidget(self.label_code, 0, 0, 1, 2) grid.addWidget(label1, 1, 0) grid.addWidget(self.code_edit, 2, 0) grid.addWidget(label2, 3, 0) grid.addWidget(button2, 4, 0, 1, 2) # 关闭窗口 button2.clicked.connect(self.close) self.setLayout(grid) def center(self): # 该语句用来计算出显示器的分辨率(screen.width, screen.height) screen = QtWidgets.QDesktopWidget().screenGeometry() size = self.geometry() self.move((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2) def get_text(self): self.out_code = self.code_edit.text() # print(self.out_code) return self.out_code def show_code_img(self): img = QtGui.QPixmap(self.img_path) self.label_code.setPixmap(img) def closeEvent(self, event): code = self.get_text() if len(code) < 4 or len(code) >= 8: QtWidgets.QMessageBox.about(self, "验证码输入错误", "请注意:\n验证码一般为4-6位,请重新输入!") event.ignore() else: event.accept()if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) center = CodeRecognition() # 改变输入的图片。 path = "image_2.png" center.img_path = path center.show_code_img() center.show() app.exec_() rec_code = center.get_text() print("识别的验证码为:", rec_code)
之前学过qt的同学看起来应该不困难,没有学过qt的同学想要入门的话建议查看官方文档或者小甲鱼论坛的pyqt连接。
最后附上完整的程序:
github完整代码
- python爬虫实战--selenium验证码保存+多线程多标签+自动点击+完整代码
- 爬虫之自动保存文档-使用python/selenium
- python爬虫实战(1)抓取网页图片自动保存
- Python爬虫实战代码
- selenium python 验证码
- 爬虫实战---python图片验证码破解,PIL和安装
- python爬虫:短代码实现多线程爬虫
- selenium自动处理验证码
- 验证码 完整代码
- Python开发爬虫完整代码解析
- Python爬虫之自动登录与验证码识别
- Python爬虫之自动登录与验证码识别
- Python爬虫之自动登录与验证码识别
- Python爬虫之自动登录与验证码识别
- python爬虫实战-自动IP地址查询
- 【python 爬虫】linux 下 selenium+phantomjs 自动模拟登陆
- Python网络爬虫实战项目代码大全
- Python网络爬虫实战项目代码大全
- React学习笔记-注释
- linux 利用iptables 端口转发
- springboot02Spring Boot JPA/Hibernate/Spring Data
- 利用Windows部署服务通过网络,批量安装Windows 7旗舰版
- 407. Trapping Rain Water II
- python爬虫实战--selenium验证码保存+多线程多标签+自动点击+完整代码
- android 四种启动模式
- 谷歌发布了一款AI工具 可以帮助基因组数据解读 | 速递
- 欢迎使用CSDN-markdown编辑器
- poj3167
- 科学家发现让人类幸福感飙升的密码!给大脑植入这个算法 | 精选
- Unity 导出 Facebook GameRoom
- java.lang.ThreadLocal 深入理解
- win 7 系统下 sublime 的 插件包安装(Install Package)解决 no package avaiable 问题