Tesseract简介+多线程
- 1. Tesseract的安装与使用
- 1.1 Tesseract简介
- 1.2 Tesseract的安装
- 1.3 Tesseract的使用
- 2. 多线程的快速入门
- 2.1 多线程基本概念
- 2.2 多线程的引出
- 3. 通过函数创建线程
- 3.1 主线程与子线程
- 3.2 查看线程的数量
- 3.3 验证子线程的创建与执行
1. Tesseract的安装与使用
1.1 Tesseract简介
有时候阻碍程序爬虫的正是在登录或者请求数据时的图形验证码。因此,笔者这里简单介绍⼀种能将图片翻译成文字的技术。将图片翻译成文字⼀般被称为光学文字识别(Optical Character Recognition),简写为OCR。
目前,实现OCR的库不是很多,特别是开源的。因为这块存在⼀定的技术壁垒(需要大量数据的支撑、要求有一定的机器学习、深度学习算法知识等),并且如果做得好就具有很高的商业价值。这里介绍⼀个比较优秀的图像识别开源库:Tesserat。
Tesseract是目前公认最优秀、最准确的开源OCR库。其具有很高的识别度,也具有很高的灵活性,它可以通过训练识别任何字体。
1.2 Tesseract的安装
- 下载安装包:
Windows系统安装,在以下链接下载安装包:https://github.com/tesseract-ocr/ 具体安装过程如下: - 这里可以选是否下载额外的语言数据,大家根据自己的需求定,笔者这就不下载了。
- 在Python中使用Tesseract前,需先进行安装
conda install pytesseract -n xxx
。 - 设置环境变量:
安装完成后,如果想要在命令行中使用Tesseract,那么应该设置环境变量。一般,Mac和Linux在安装的时候就默认已经设置好了。在Windows下需要把tesseract.exe所在的路径添加到PATH环境变量中。 - 为方便使用,还有⼀个需要设置的环境变量是:添加训练的数据文件路径。
- 验证安装:
进⼊cmd,输入命令tesseract --version
查看版本,正常运行则安装成功。
1.3 Tesseract的使用
在命令行中使用Tesseract:
验证码图片如下:
结果保存到当前工作路径,自动保存为txt文本,其中内容如下:
7 3 6 4
在Python中使用Tesseract识别图形验证码:
import pytesseract
from PIL import Image # 是python图像处理库
# 指定tesseract的驱动
pytesseract.pytesseract.tesseract_cmd = r'D:\Program Files\Tesseract-OCR\tesseract.exe'
# 指定tesseract的训练数据的路径
tessdata_dir_config = r'--tessdata-dir "D:\Program Files\Tesseract-OCR\tessdata"'
# 从本地导入图片
image = Image.open('demo.jpg')
# 输出结果
print(pytesseract.image_to_string(image, lang='eng', config=tessdata_dir_config))
运行结果:
需要注意的是,Tesseract对于一些简单的图片识别率比较高,而对于一些比较复杂的图片验证码也是无能为力的。
2. 多线程的快速入门
2.1 多线程基本概念
多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。多线程是在同一时间需要完成多项任务时而出现的产物。
在日常生活中的许多事情是同时进行的,比如开车的时候,手和脚共同来驾驶汽车,再比如唱歌跳舞也是同时进行的。
爬虫为什么要学习多线程:为了提高爬虫的效率。
2.2 多线程的引出
通常情况下,代码是按顺序执行的:
import time
def sing():
for i in range(3):
print('John is singing.')
time.sleep(1)
def dance():
for i in range(3):
print('John is dancing.')
time.sleep(1)
if __name__ == '__main__':
sing()
dance()
运行结果:
上述结果显示,先完成唱歌任务,再执行跳舞任务,不符合期望的唱歌跳舞任务同时进行。
如何创建线程:
- 通过函数,使用Threading模块中的Thread类,在这个类中有一个target参数需要传递一个函数对象,而这个函数里面就是多线程的逻辑
- 通过类的方式,这个类需要继承threading.Thread,并实现(重写)run(),在run方法中实现多线程的逻辑。
以上两种方式都可以实现多线程,一般情况下我们会选择第二种方式,因为当多线程的逻辑较为复杂时,采用面向对象的方式编写代码会方便后续的维护以及复用。
代码需求:创建多线程,线程里执行print语句。
import time
import threading
def demo():
print('Hello, this is child thread.')
if __name__ == '__main__':
for i in range(5):
# 每执行一次循环,就创建一个线程
t = threading.Thread(target=demo)
t.start()
time.sleep(1)
运行结果:
3. 通过函数创建线程
3.1 主线程与子线程
一般情况下,main()方法中执行的是主线程,Thread()创建的线程称为子线程。
示例代码:
import time
import threading
def demo():
for i in range(5):
print('Hello, this is child thread.')
time.sleep(1)
if __name__ == '__main__':
t = threading.Thread(target=demo)
t.start() # 开始执行子线程
print('Hello, this is main thread.')
运行结果:
上述结果中,主线程的print语句结束后,程序并没有退出,而是等到demo的for循环结束后再退出,说明主线程会等待子线程执行结束之后,主线程再结束。
如果希望子线程执行结束后,再执行主线程,可以使用join()方法。
if __name__ == '__main__':
t = threading.Thread(target=demo)
t.start() # 开始执行子线程
t.join() # 等待子线程结束,再执行后续代码
print('Hello, this is main thread.')
运行结果:
join()方法的作用是等待子线程执行结束后,再让主线程继续执行。
如果希望主线程执行结束就退出,而不想等待子线程,可以使用setDaemon()方法。
if __name__ == '__main__':
t = threading.Thread(target=demo)
t.setDaemon(True) # 不会等待子线程
t.start() # 开始执行子线程
print('Hello, this is main thread.')
运行结果:
现在,可以实现一边唱歌、一边跳舞的逻辑了:
import time
import threading
def sing():
for i in range(3):
print('John is singing.')
time.sleep(1)
def dance():
for i in range(3):
print('John is dancing.')
time.sleep(1)
def main():
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
if __name__ == '__main__':
main()
运行结果:
3.2 查看线程的数量
使用threading.enumerate()
查看当前线程的数量,该方法返回的是所有当前存活的线程对象的列表。
示例代码:
import threading
def demo1():
for i in range(5):
print('This is demo1:%d' % i)
def demo2():
for i in range(5):
print('This is demo2:%d' % i)
def main():
t1 = threading.Thread(target=demo1)
t2 = threading.Thread(target=demo2)
t1.start()
t2.start()
# 打印当前存活的线程
print(threading.enumerate())
if __name__ == '__main__':
main()
运行结果:
主线程打印的结果,列表里只有两个线程对象,分别为主线程和子线程2,原因是子线程1已经执行完毕,看不到想要的效果。
为了更好地看出线程存活的情况下,现做如下改进:
- 每个子线程执行时加入延时;
- 第二(一)个子线程增加循环次数;
- 主线程添加循环,查看线程存活数量。
改进后的代码:
import threading
import time
def demo1():
for i in range(3):
print('This is demo1:%d' % i)
time.sleep(1)
def demo2():
for i in range(6):
print('This is demo2:%d' % i)
time.sleep(1)
def main():
t1 = threading.Thread(target=demo1)
t2 = threading.Thread(target=demo2)
t1.start()
t2.start()
# 打印当前存活的线程
while True:
if len(threading.enumerate()) == 1:
# 列表里只有一个线程对象,说明只剩下主线程
print(threading.enumerate())
break
print(threading.enumerate())
time.sleep(1)
if __name__ == '__main__':
main()
运行结果:
3.3 验证子线程的创建与执行
现在,仔细地探讨一下子线程的创建与执行。
示例代码:
import time
import threading
def demo():
for i in range(3):
print('This is child thread: %d' % i)
time.sleep(1)
def main():
# 每一步执行时都打印存活的线程,验证线程的创建和执行
print(threading.enumerate())
t_obj = threading.Thread(target=demo)
print(threading.enumerate())
t_obj.start()
print(threading.enumerate())
if __name__ == '__main__':
main()
运行结果:
main()中第一个print语句显示当前只有主线程,第二个print语句在threading.Thread(target=demo)
语句之后,但是结果显示仍然只有主线程,直到调用start()方法后,才显示有两个线程。所以,start()方法才是真正地创建并执行子线程。其实, threading.Thread(target=demo)
的作用类似于向系统注册一个回调函数,目的是告诉子线程的执行逻辑。