一.多线程爬虫
threading模块
该模块是python中专门提供用来做多线程的模块 threading模块最常用的类Thread
- 传统方式
import threading
import time
def coding():
for x in range(3):
print('正在编写代码%s' %x)
time.sleep(1)
def drawing():
for i in range(3):
print('正在编译%s' %i)
time.sleep(1)
def main():
coding()
drawing()
if __name__ == '__main__':
main()
**多线程方式**
ex:
import threading
import time
def coding():
for x in range(3):
print('正在编写代码%s' %x)
time.sleep(1)
def drawing():
for i in range(3):
print('正在编译%s' %i)
time.sleep(1)
def main():
t1 = threading.Thread(target=coding)
t2 = threading.Thread(target=drawing)
t1.start()
t2.start()
if __name__ == '__main__':
main()
-
使用Thread类创建多线程:
-
查看线程数: 使用threading.enumerate()函数可以看到当前线程的数量
-
查看当前线程的名字: 使用threading.current_thread()可以看到当前线程的信息
-
继承自threading.Thread类:为了让线程代码更好的封装 可以使用threading模块下的Thread类 继承自这个类
然后实现run方法 线程就会自动运行ran方法中的代码import threading import time class CodingThread(threadimg.Thread): def run(self): for x in range(3): print('正在编写代码%s' %threading.current_thread()) time.sleep(1) class DrawingThread(threading.Thread): def run(self): for i in range(3): print('正在编译%s' %threading.Thread()) time.sleep(1) def main(): t1 = DrawingThread() t2 = CodingThread() t1.start() t2.start() if __name__ == '__main__': main()
-
-
多线程共享全局变量以及锁机制:多线程都是在同一个进程中进行的 因此进程中的全局变量所用线程都是可共享的
又因为线程的执行顺序是无序的 有可能造成数据错误import threading VALUSE = 0 def add_value(): global VALUE for i in range(100): VALUE +=1 print('value: %d'%VALUE) def main(): for x in range(2): t = threading.Thread(target=add_value) t.start() if __name__ == '__main__' main()
解决方法:
import threading
VALUSE = 0
#创建一个锁
glock = threading.Lock()
def add_value():
global VALUE
#加锁
glock.acquire()
for i in range(100):
VALUE +=1
#释放锁
glock.release()
print('value: %d'%VALUE)
def main():
for x in range(2):
t = threading.Thread(target=add_value)
t.start()
if __name__ == '__main__'
main()
-
Lock版生产者和消费者模式:threading.Lock锁实现的生产者与消费者模式
import threading gMoney = 1000 gLock = threading.Lock gTotalTimes = 10 gTimes = 0 class Producer(threading.Thread): def run(self): global gMoney global gTimes #持续生产 while True: money = random.randint(100,1000) gLock.acquire() if gLock >=10: glock.release() break gMoney +=money print('%s生产了%d元钱,剩余%d元钱'%(threading.curre_thread(),money,gMoney)) gTimes +=1 gLock。release() time.sleep(1) class Consumer(threading.Thread): def run(self): global gMoney while True: money = random.randint(100,1000) gLock.acquire() #需要做一下判断 if gMoney>= money gMoney -=money print('%消费了%d元钱,剩余%d元钱'%(threading.curre_thread(),money,gMoney)) else: if gTimes >=gTotalTimes: gLock.release() print('您的余额不足') gLock。release() time.sleep(1) def main(): #5个生产进程 for x in range(5): t = Product() t.start() #三个消费进程 for x in range(3): t = Consumer() t.start() if __name__ == '__main__()' main()
Condition 版的生产者与消费者模式:优化版模式
Lock版本的生产者与消费者模式 可以正常运行 但是纯在一个不足 在消费者中 总是通过while True死循环并且上锁的方式去判断
钱够不够 上锁是一个很费cpu自愿的行为 相比较Condition版是一个更好的方式 用threading.Condition来实现
并且 threading.Condition可以在没有数据的时候处于阻塞等待状态 一旦有了合适的数据 还可以使用notify相关的函数来通知其他处于等待状态的线程
这样就可以不用做一些无用的上锁和解锁的操作 来提高程序的性能
5. threading.Condition相关函数的介绍:
- acquire :上锁
- release :解锁
- wait :将当前线程处于等待状态 并且会释放锁 可以被其他线程使用notify 和notify_all函数唤醒 被唤醒后会继续等待上锁 上锁后继粗制星系买你的代码
- notify :通知某个正在等待的线程 默认是第一个等待的线程
- notify_all :通知等待所有正在等待的线程 notify 和notify_all 不会释放锁 并且需要再release 之前调用
import threading
import random
import time
gMoney = 1000
gContion = threading.conditon
gTotalTimes = 10
gTimes = 0
class Producer(threading.Thread):
def run(self):
global gMoney
global gTimes
#持续生产
while True:
money = random.randint(100,1000)
gContdiion.acquire()
if gLock >=10:
glock.release()
break
gMoney +=money
print('%s生产了%d元钱,剩余%d元钱'%(threading.curre_thread(),money,gMoney))
gTimes +=1
#唤醒线程
gCondition.notify_all()
gCondition.release()
time.sleep(1)
class Consumer(threading.Thread):
def run(self):
global gMoney
while True:
money = random.randint(100,1000)
gContion.acquire()
#只要金钱不足就让线程处于阻塞状态
while gMoney <money:
#余额不足可能是生产者生产够10次了,那么就要结束这个线程了
if gTimes>=gTotalTimes:
#结束线程之前要释放锁
gCondition.release()
#不能用break
return
print('您的余额不足')
#处于阻塞状态
gCondition.wait()
gMoney -=money
print('%消费了%d元钱,剩余%d元钱'%(threading.curre_thread(),money,gMoney))
gContdition.release()
time.sleep(1)
def main():
#5个生产进程
for x in range(5):
t = Product()
t.start()
#三个消费进程
for x in range(3):
t = Consumer()
t.start()
if __name__ == '__main__()'
main()
-
Queue 线程安全队列:
在线程中 访问一些全局变量 枷锁是一个经常的过程 如果你是想把一些数据存储到某个队列中 python中内置了一个线程安全的模块,叫Queue模块 该模块提供了同步的线程安全的队列类 包括FIFO(先进先出)队列Queue LIFO(后进先出)队列lifoQueue 这些队列实现了锁原语
能够在多线程中直接使用 可以使用队列来实现进程间的同步相关函数:
- 初始化Queue(maxsize) :创建一个先进先出的队列
- qsize() :返回队列的大小
- empty() :判断队列是否为空
- full :判断队列是否满了
- get() :从队列中去最后一个数据
- put() :将一个数据放到队列中
from queue inmport Queue
import time
def set_value(q):
index = 0
while True:
q.put(index)
index +=1
time.sleep(2)
def get_value(q):
while True:
print(q.get())
def main():
q = Queue(4)
t1 = threadingThread(target=set_value,args=[q])
t2 = threadingThread(target=get_value,args=[q])
t1.start()
t2.start()
if __name__ =='__main__':
main()**
- GIL解释器锁:
python自带的解释器是cpython cpython解释其的多线程实际上并不是一个真正的多线程 (在多核CPU中 只能利用一核 )
同一时刻只有一个线程在执行 为了保证同一时刻只有一个线程在执行 cpython解释器中有一个叫GIL的东西 除了cpython解释器还有其他解释器
这些解释器是没有GIL锁的
JPython : 用Java实现的解释器 不存在GIL锁
IronPython :用.net实现的解释器 不存在GIL锁
pypy :用pyhton实现的解释器 不存在GIL锁
二.AJAX
异步JavaScript和XML 在后台与服务器进行少量数据交换 Ajax可以使网页实现异步更新 (不需要重新加载整个网页的情况下 对网页的某部分进行更新 )传统传输数据格式方面 使用的是xml语法
其实现在在数据交互基本上都是JSON 使用AJAX加载的数据 即使使用了JS 将数据渲染到浏览器中 在网页代码中还是不能看到ajax加载的数据 只能看到使用url加载的html代码
获取ajax数据的方式:
- 直接使用Ajax调用的接口 然后通过这个接口去请求这个接口
优点 :直接可以请求到数据 代码量少 性能高
缺点 :分析接口比较复杂 特别是一些通过js混淆的接口 要有一定的j功底 容易被发现是爬虫 - 使用Selenium+chromedriver模拟浏览器行为获取数据
优点: 直接模拟浏览器的行为 爬虫更稳定
缺点:代码量多 性能低
使用Ajax接口
抓包神器Fiddler
Fiddler是一款强大Web调试工具,它能记录所有客户端和服务器的HTTP请求。 Fiddler启动的时候,默认IE的代理设为了127.0.0.1:8888,而其他浏览器是需要手动设置。
1. 工作原理
Fiddler 是以代理web服务器的形式工作的,它使用代理地址:127.0.0.1,端口:8888
2. Fiddler抓取HTTPS设置
启动Fiddler,打开菜单栏中的 Tools > Telerik Fiddler Options,打开“Fiddler Options”对话框。
3. 对Fiddler进行设置:
- 打开工具栏->Tools->Fiddler Options->HTTPS,
- 选中Capture HTTPS CONNECTs (捕捉HTTPS连接),
- 选中Decrypt HTTPS traffic(解密HTTPS通信)
- 另外我们要用Fiddler获取本机所有进程的HTTPS请求,所以中间的下拉菜单中选中…from all processes (从所有进程)
- 选中下方Ignore server certificate errors(忽略服务器证书错误)
4. 为 Fiddler 配置Windows信任这个根证书解决安全警告:Trust Root Certificate(受信任的根证书)
5. Fiddler 主菜单 Tools -> Fiddler Options…-> Connections
- 选中Allow remote computers to connect(允许远程连接)
- Act as system proxy on startup(作为系统启动代理)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200809180334894.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L25pbnBlbmd5b3U=,size_16,color_FFFFFF,t_70)
6. 重启Fiddler,使配置生效(这一步很重要,必须做)。
- Fiddler 如何捕获Chrome的会话
-
安装SwitchyOmega 代理管理 Chrome 浏览器插件
-
如图所示,设置代理服务器为127.0.0.1:8888
- 通过浏览器插件切换为设置好的代理。
- 通过浏览器插件切换为设置好的代理。
-
- Fiddler界面
- 设置好后,本机HTTP通信都会经过127.0.0.1:8888代理,也就会被Fiddler拦截到。
- 请求 (Request) 部分详解
Headers —— 显示客户端发送到服务器的 HTTP 请求的 header,显示为一个分级视图,包含了 Web 客户端信息、Cookie、传输状态等。
Textview —— 显示 POST 请求的 body 部分为文本。
WebForms —— 显示请求的 GET 参数 和 POST body 内容。
HexView —— 用十六进制数据显示请求。
Auth —— 显示响应 header 中的 Proxy-Authorization(代理身份验证) 和 Authorization(授权) 信息.
Raw —— 将整个请求显示为纯文本。
JSON - 显示JSON格式文件。
XML —— 如果请求的 body 是 XML 格式,就是用分级的 XML 树来显示它。 - 响应 (Response) 部分详解
Transformer —— 显示响应的编码信息。
Headers —— 用分级视图显示响应的 header。
TextView —— 使用文本显示相应的 body。
ImageVies —— 如果请求是图片资源,显示响应的图片。
HexView —— 用十六进制数据显示响应。
WebView —— 响应在 Web 浏览器中的预览效果。
Auth —— 显示响应 header 中的 Proxy-Authorization(代理身份验证) 和 Authorization(授权) 信息。
Caching —— 显示此请求的缓存信息。
Privacy —— 显示此请求的私密 (P3P) 信息。
Raw —— 将整个响应显示为纯文本。
JSON - 显示JSON格式文件。
XML —— 如果响应的 body 是 XML 格式,就是用分级的 XML 树来显示它 。
- 设置好后,本机HTTP通信都会经过127.0.0.1:8888代理,也就会被Fiddler拦截到。
Selenium+chromedriver模拟浏览器行为获取数据
Selenium+chromedriver 获取动态数据:
Selenium相当于一个机器人 可以模拟人类在浏览器的一些行为 自动处理浏览器上的一些行为 比如点击 填充数据 删除cookie等
chromedriver 是一个驱动chrome浏览器的驱动程序 使用它才可以驱动浏览器 不同浏览器有不同的driver
具体浏览器的driver安装问题 自行百度查找-*-
安装Selenium和chromedriver :
1.安装Selenium selenium有很多语言的版本 有Java ruby python :pip install selenium
2.安装chrome driver 下载完成后放到不需要权限的纯英文目录下就可以了
```python
from selenium import webdriver
driver_path = r'path(路径)\chromedriver.exe'
driver = webdriver.chrome(executable_path=driver_path)
driver.get('url')
不会返回数据 想要获取数据 需要用属性来获取
#获取网页源代码
print(driver.page_source)
```
selenium常用的操作:
- **关闭页面:**
driver.close() :关闭当前页面
driver.quit() :退出整个浏览器
- **定位元素:**
- find_element_by_id:根据id查找元素 :
submittag = driver.find_element_by_id('jhin')
submittag1 = driver.find_element(By.ID,'jhin')
- find_element_by_class_name:根据类名查找元素:
submittag = driver.find_element_by_name('jhin')
submittag1 = driver.find_element(By.CLASS_NAME,'jhin')
- find_element_by_name :根据name属性的值来查找元素
submittag = driver.find_element_by_name('jhin')
submittag1 = driver.find_element(By.NAME,'jhin')
- find_element_by_tag_name :根据标签名属性的值来查找元素
submittag = driver.find_element_by_tag_name('jhin')
submittag1 = driver.find_element(By.TAG_NAME,'jhin')
- find_element_by_xpath:根据xpath语法来获取元素
submittag = driver.find_element_by_xpath('//jhin')
submittag1 = driver.find_element(By.XPATH,'//jhin')
- find_element_by_css_selector :根据css选择器选择元素
submittag = driver.find_element_by_css_selector('//jhin')
submittag1 = driver.find_element(By.CSS_SELECTOR,'//jhin')
find_element是获取第一个满足条件的元素 find_elements是获取所有满足条件的元素
- **如果只想要解析网页中的数据 推荐将网页源代码传递给lxml解析 解析效率更高**
html = etree.HTML(driver.page_source)
html.xpath('xxxx')
4. **对元素进行一些操作 比如给一个文本框输入值 或者点击某个按钮 推荐使用selenium提供的查找元素的方法**
- #输入值
inputtag = driver.find_element_by_id('kw')
inputtag.send_keys('python')
- 操作表单元素:
常见的表单元素:
input type='text/password/email/number'
button input[type='submit']
checkbox input='checkbox'
- 操作输入框:第一步:找到这个元素 第二步使用send_keys(value)将数据填充进去
inputtag = driver.find_element_by_id('jhin')
inputtag.send_keys('python')
- 使用clear方法可以清楚输入框的中的内容:
inputtag.clear()
- 操作checkbox:因为要选中checkbox标签 在网页中是通过鼠标点击的 因此想要选中
checkbox标签 那么要先选中这个标签 然后执行click事件 :比如勾选记住密码的选项 。。。
remembertag = driver.finder_element_by_name(‘jhin’)
ramembertag.click()
- 选择select select元素不能直接点击 因为点击后还需要选中元素 selenium专门为select标签提供了一个selenium.webdriver.support.ui.select
将获取到的元素当成参数传到这个类 创建这个对象 以后就可以使用这个对象进行选择了
比如网站的外链接
```python
#操作select标签
from selenium import webdriver
form selenium.webdriver.support.ui import Select
driver_path = r'path(路径)/chromdriver.exe'
driver = webdriver.chrome(executable_path=driver_path)
driver.get('http://www.doubai.cn')
selectBtn = Select(driver.find_element_by_name('xxx'))
#选择第一个外链网站
selectBtn.select_by_index(1)
selectBtn.select_by_visible_text('杰')
selectBtn.select_by_value('url')
```
```python
#百度收索
from selenium import webdriver
form selenium.webdriver.support.ui import Select
driver_path = r'path(路径)/chromdriver.exe'
driver = webdriver.chrome(executable_path=driver_path)
driver.get('http://www.baidu.cn')
inputtag = driver.find_element_by_id('kw')
inputtag.send_keys('python')
submittag = driver.find_element_by_id('su')
submittag.click()
```
- selenium行为链:有的时候在页面中的操作可能要很多步 可以用鼠标行为链ActionChins来完成
比如现在要将鼠标移动到某个元素来执行点击事件
```python
from selenium import webdriver
from selenium.webdrive.common.action_chains import Actions
import time
driver_path = r'path(路径)/chromdriver.exe'
driver = webdriver.chrome(executable_path=driver_path)
driver.get('http://www.baidu.com/')
inputtag = driver.find_element_by_id('kw')
submittag = driver.find_element_by_id('su')
actions = ActionChains(driver)
#把鼠标移动到标签上
actions.move_to_element(inputtag)
actions.send_keys_to_element(inputtag,'python')
#移动到收索 然后点击
actions.move_to_element(submittag)
actions.click(submittag)
#执行
actions.perform()
click_and_hold(element):点击点不松开鼠标
context_click(element):右键点击
double_click(element):双击
``
5. **Cookie操作:**
- 获取所有的cookie:
for cookie in driver.get_cookies()
print(cookie)
- 根据cookie的key获取value:
value = driver.get_cookie(key)
- 删除所有的cookie:
dirver.delete_all_cookies()
- 删除某个cookie:
driver.delete_cookie(key)
```python
from selenium import webdriver
from selenium.webdrive.common.action_chains import Actions
import time
driver_path = r'path(路径)/chromdriver.exe'
driver = webdriver.chrome(executable_path=driver_path)
driver.get('http://www.baidu.com/')
for cookie in driver.get_cookies()
print(cookie)
print(driver.get_cookies('jhin'))
dirver.delete_all_cookies('jhin')
dirver.delete_all_cookies()
```
6. **页面等待:现在的网页越来越多的采用了Ajax技术 这样程序便不能确定何时某个元素完全加载出来**
如果实际页面等待时间过长 导致某个要找的元素还没出来 但是你的代码去直接使用了webElement
那么就会抛出异常 因此 selenium提供了两个等待方式 隐式等待 以及显示等待
隐式等待:调用driver。implictly_wait 那么获取不同的元素之前 会等待10秒
```python
from selenium import webdriver
driver_path = r'path(路径)/chromdriver.exe'
driver = webdriver.chrome(executable_path=driver_path)
driver.get('http://www.baidu.com/')
#等待20秒
driver.implicitly_wait(20)
driver.find_element_by_id('kw')
```
7. **显式等待:显式等待是表明某个条件成立后才执行的获取元素的操作 也可以在等待的时候指定一个最大的时间**
如果超过这个时间会抛出一个异常 显式等待应该使用 selenium.webdriver.excepted_conditions期望的条件
和selenium.webdriver.support.ui.webDriverWait来配合完成
```python
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expect_condition as EC
from selenium.webdriver.common.by import By
driver_path = r'path(路径)/chromdriver.exe'
driver = webdriver.chrome(executable_path=driver_path)
driver.get('http://www.baidu.com/')
WebDriverWait(driver,10).until(
#等到元素出现时
EC.persence_of_element_located((By.ID,'jhin'))
```
8. **其他一些等待条件:**
presence_of_element_located:某个元素加载完毕了
presence_of_element_all_located:网页中所有满足条件的某个元素加载完毕了
element_to_be_cliable:某个元素是可以点击了
9. **selenium 打开多窗口 和切换窗口:**
1. 切换页面: 有时候窗口中有很多tab页面 需要进行页面的切换 selenium提供了一个叫做switch_to_window 来进行切换
具体切换到那个页面 可以从driver.window_handles中找到
```python
from selenium import webdriver
driver_path = r'path(路径)/chromdriver.exe'
driver = webdriver.chrome(executable_path=driver_path)
driver.get('http://www.baidu.com/')
#打开一个新的页面
driver.execute_script("window.open('http://wwww.douban.com')")
print(driver.current_url)
#切换到这个新的页面中
driver.seitch_to_window(driver.window_handles[1]) #window_handles[x] x是页面间的距离 起始页是0
```
2. 虽然在窗口中切换到了新的页面 但是driver中还没有切换
如果想要在代码中切换到新的页面 并且做一些爬虫 那么应该使用driver.switch_to_window 来切换到制定的窗口
driver.window_handlers中提取具体的第几个窗口
WebElement 元素:
from selenium。webdriver.remote.webelement import WebElement 类是每个获取出来的元素的所属类:
10. 一些常用的属性:
get_attribute:这个标签的某个属性的值
screenshot:获取当前页面的截图 这个方法只能在driver上使用
driver的对象类 也是继承自WebElement
设置代理ip: 不同浏览器有不同的实现方式
以chrome浏览器为例:
```python
from selenium import webdriver
driver_path = r'path(路径)/chromdriver.exe'
options = webdriver.ChromeOptions()
options.add_argument("--proxy--server=http://ip:port")
driver = webdriver.Chrome(executable_path=driver_path,chrome_options=options)
driver.get('http://httpbin.org/ip')
```
三.图形验证码识别技术
开源库Tesseract
将图片翻译成文字一般称为光学文字识别 简称OCR 但是要有一定的技术(大量的数据 算法 机器学习 深度学习)
Tesseract:是一个OCR库 目前由谷歌赞助 公认的最优秀 最准确的OCR库
安装:
windowa系统: 下载可执行文件 然后放在不需要权限的目录下 https://digi.bib.uni-mannheim.de/tesseract/
linux系统 :https://github.com/tesseract.ocr/tesseract/wiki/Compling ubuntu下可以通过命令安装 sudo apt install tesseract-ocr
- 设置环境变量 :
想要在命令行中使用tesseract 在widows下应该设置环境变量 而linux和mac系统在安装的时候就默认设置好了
一共需要设置两个环境变量 第一个就是将exe文件所在的路径添加到path环境变量中去 还有一个就是需要吧训练的数据文件路径也放到环境变量中 在环境变量中 添加一个TESSDATA_PREFIX=path/tessdata
python代码中操作tesseract 需要安装一个库 pytesseract 通过pip 可以安装
并且需要安装第三方库PIL
#使用pytesseract 将图片上的文字转换为文本
import pytesseract
from PIL import Image
#指定tesseract。exe所在的路径
pyttesseract.pytesseract.tesseract_cmd = r'path\tesseract.exe'
#打开图片
image = Image.open('filename')
#调用image_to_string将图片转换为文本
text = pytesseract.image_to_string(Image.open('imagepath'))
print(text)
- tesseract 处理网站验证码:
import pytesseract
from urllib import request
from PIL import Image
import time
def main():
pyttesseract.pytesseract.tesseract_cmd = r'path\tesseract.exe'
url = '验证码url'
while True:
request.urlretrieve(url,'image_name.png')
image = Image.open('image_name.png')
text = pytesseract.image_to_string(Image.open('imagepath'))
print(text)
#避免请求太频繁
time.sleep(2)
if __name__ == '__main()__'