监控 Python 内存使用情况和代码执行时间

我的代码的哪些部分运行时间最长、内存最多?我怎样才能找到需要改进的地方?”

在开发过程中,我很确定我们大多数人都会想知道这一点,在本文中总结了一些方法来监控 Python 代码的时间和内存使用情况。

本文将介绍4种方法,前3种方法提供时间信息,第4个方法可以获得内存使用情况。

  • time 模块
  • %%time 魔法命令
  • line_profiler
  • memory_profiler

 1. time 模块

这是计算代码运行所需时间的最简单、最直接(但需要手动开发)的方法。他的逻辑也很简单:记录代码运行之前和之后的时间,计算时间之间的差异。这可以实现如下:

 import time

 start_time = time.time()
 result = 5+2
 end_time = time.time()

 print('Time taken = {} sec'.format(end_time - start_time))

下面的例子显示了for循环和列表推导式在时间上的差异:

 import time

 # for loop vs. list comp
 list_comp_start_time = time.time()
 result = [i for i in range(0,1000000)]
 list_comp_end_time = time.time()
 print('Time taken for list comp = {} sec'.format(list_comp_end_time - list_comp_start_time))

 result=[]
 for_loop_start_time = time.time()
 for i in range(0,1000000):
     result.append(i)
 for_loop_end_time = time.time()
 print('Time taken for for-loop = {} sec'.format(for_loop_end_time - for_loop_start_time))

 list_comp_time = list_comp_end_time - list_comp_start_time
 for_loop_time = for_loop_end_time - for_loop_start_time
 print('Difference = {} %'.format((for_loop_time - list_comp_time)/list_comp_time * 100))

我们都知道for会慢一些

 Time taken for list comp = 0.05843973159790039 sec
 Time taken for for-loop = 0.06774497032165527 sec
 Difference = 15.922795107582594 %

 2. %%time 魔法命令

魔法命令是IPython内核中内置的方便命令,可以方便地执行特定的任务。一般情况下都实在jupyter notebook种使用。

在单元格的开头添加%%time ,单元格执行完成后,会输出单元格执行所花费的时间。

 %%time
 def convert_cms(cm, unit='m'):
     '''
    Function to convert cm to m or feet
    '''
     if unit == 'm':
         return cm/100
     return cm/30.48

 convert_cms(1000)

结果如下:

 CPU timesuser 24 µssys: 1 µstotal: 25 µs
 Wall time: 28.1 µs

 Out[8]: 10.0

这里的CPU times是CPU处理代码所花费的实际时间,Wall time是事件经过的真实时间,在方法入口和方法出口之间的时间。

 3. line_profiler

前两个方法只提供执行该方法所需的总时间。通过时间分析器我们可以获得函数中每一个代码的运行时间。

这里我们需要使用line_profiler包。使用pip install line_profiler。

 import line_profiler

 def convert_cms(cm, unit='m'):
     '''
    Function to convert cm to m or feet
    '''
     if unit == 'm':
         return cm/100
     return cm/30.48

 # Load the profiler
 %load_ext line_profiler

 # Use the profiler's magic to call the method
 %lprun -f convert_cms convert_cms(1000'f')

输出结果如下:

 Timer unit: 1e-06 s

 Total time: 4e-06 s
 File: /var/folders/y_/ff7_m0c146ddrr_mctd4vpkh0000gn/T/ipykernel_22452/382784489.py
 Function: convert_cms at line 1

 Line #     Hits         Time Per Hit   % Time Line Contents
 ==============================================================
      1                                           def convert_cms(cm, unit='m'):
      2                                               '''
      3                                               Function to convert cm to m or feet
      4                                               '''
      5         1         2.0     2.0     50.0     if unit == 'm':
      6                                                   return cm/100
      7         1         2.0     2.0     50.0     return cm/30.48

可以看到line_profiler提供了每行代码所花费时间的详细信息。

  • Line Contents :运行的代码
  • Hits:行被执行的次数
  • Time:所花费的总时间(即命中次数x每次命中次数)
  • Per Hit:一次执行花费的时间,也就是说 Time =  Hits X Per Hit
  • % Time:占总时间的比例

可以看到,每一行代码都详细的分析了时间,这对于我们分析时间相当的有帮助。

 4. memory_profiler

与line_profiler类似,memory_profiler提供代码的逐行内存使用情况。

要安装它需要使用pip install memory_profiler。我们这里监视convert_cms_f函数的内存使用情况

 from conversions import convert_cms_f
 import memory_profiler

 %load_ext memory_profiler

 %mprun -f convert_cms_f convert_cms_f(1000'f')

convert_cms_f函数在单独的文件中定义,然后导入。结果如下:

 Line #   Mem usage   Increment Occurrences   Line Contents
 =============================================================
      1     63.7 MiB     63.7 MiB           1   def convert_cms_f(cm, unit='m'):
      2                                             '''
      3                                             Function to convert cm to m or feet
      4                                             '''
      5     63.7 MiB     0.0 MiB           1       if unit == 'm':
      6                                                 return cm/100
      7     63.7 MiB     0.0 MiB           1       return cm/30.48memory_profiler 提供对每行代码内存使用情况的详细了解。

这里的1 MiB (MebiByte) 几乎等于 1MB。1 MiB  = 1.048576 1MB

但是memory_profiler 也有一些缺点:它通过查询操作系统内存,所以结果可能与 python 解释器略有不同,如果在会话中多次运行 %mprun,可能会注意到增量列报告所有代码行为 0.0 MiB。这是因为魔法命令的限制导致的。

虽然memory_profiler有一些问题,但是它就使我们能够清楚地了解内存使用情况,对于开发来说是一个非常好用的工具

 5. 总结一下

虽然Python并不是一个以执行效率见长的语言,但是在某些特殊情况下这些命令对我们还是非常有帮助的。

转自:https://mp.weixin.qq.com/s/oKm5oDVsXxX8UZd7FUQaFg

抓取速度提升 3 倍!Python 的这个内置库你用上了吗?

从网站中抓取数据是开发者的一个典型“用例”。无论它是属于副业项目,还是你正在成立一个初创公司,抓取数据似乎都很有必要。

举个例子,倘若您想要创建一个比价网站,那么您会需要从各种电商网站上抓取价格信息;或者您想要构建一个可以识别商品并在亚马逊上自动查找价格的“人工智能”。类似的场景还有很多。

但是您有没有注意到,获取所有页面信息的速度有多慢呢?您会选择一个接一个地去抓取商品吗?应该会有更好的解决方案吧?答案是肯定的。

抓取网页可能非常耗时,因为您必须花时间等待服务器响应,抑或是速率受限。这就是为什么我们要向您展示如何通过在 Python 中使用并发来加速您的网页数据抓取项目

前提

为了使代码正常运行,您需要安装 python 3[1]。部分系统可能已经预装了它。然后您还需要使用 pip install 安装所有必要的库。

pip install requests beautifulsoup4 aiohttp numpy

如果您了解并发背后的基础知识,可以跳过理论部分直接进入实际操作环节。

并发

并发是一个术语,用于描述同时运行多个计算任务的能力。

当您按顺序向网站发出请求时,您可以选择一次发出一个请求并等待结果返回,然后再发出下一个请求。

不过,您也可以同时发送多个请求,并在它们返回时处理对应的结果,这种方式的速度提升效果是非常显著的。与顺序请求相比,并发请求无论是否并行运行(多个 CPU),都会比前者快得多 — 稍后会详细介绍。

要理解并发的优势。我们需要了解顺序处理和并发处理任务之间的区别。假设我们有五个任务,每个任务需要 10 秒才能完成。当按顺序处理它们时,完成五个任务所需的时间为 50 秒;而并发处理时,仅需要 10 秒即可完成。

抓取速度提升 3 倍!Python 的这个内置库你用上了吗?

除了提高处理速度之外,并发还允许我们通过将网页抓取任务负载分布于多个进程中,来实现在更短的时间内完成更多的工作。

这里有几种实现并行化请求的方式:例如 multiprocessingasyncio。从网页抓取的角度来看,我们可以使用这些库来并行处理对不同网站或同一网站不同页面的请求。在本文中,我们将重点关注 asyncio,这是一个 Python 内置的模块,它提供了使用协程编写单线程并发代码的基础设施。

由于并发意味着更复杂的系统和代码,因此在使用前请考虑在您的使用场景中是否利大于弊。

并发的优势

  • 在更短的时间内完成更多的工作
  • 可以将空闲的网络时间投入到其他请求中

并发的危险之处

  • 更不易于开发和调试
  • 可能存在竞争条件
  • 需要检查并使用线程安全的函数
  • 一不小心就会增加程序阻塞的概率
  • 并发自带系统开销,因此需要设置合理的并发级别
  • 针对小型站点请求过多的话,可能会变成 DDoS 攻击

抓取速度提升 3 倍!Python 的这个内置库你用上了吗?

 

*同时释放所有请求时要小心*

 

为何选择 asyncio

在做出选择之前,我们有必要了解一下 asynciomultiprocessing 之间的区别,以及 IO 密集型与 CPU 密集型之间的区别。

asyncio[2] “是一个使用 async/await 语法编写并发代码的库”,它在单个处理器上运行。

multiprocessing[3] “是一个支持使用 API 生产进程的包 […] 允许程序员充分利用给定机器上的多个处理器”。每个进程将在不同的 CPU 中启动自己的 Python 解释器。

IO 密集型意味着程序将受 I/O 影响而变得运行缓慢。在我们的案例中,主要指的是网络请求。

CPU 密集型意味着程序会由于 CPU 计算压力导致运行缓慢 — 例如数学计算。

为什么这会影响我们选择用于并发的库?因为并发成本的很大一部分是创建和维护线程/进程。对于 CPU 密集型问题,在不同的 CPU 中拥有多个进程将会提升效率。但对于 I/O 密集型的场景,情况可能并非如此。

由于网页数据抓取主要受 I/O 限制,因此我们选择了 asyncio。但如果有疑问(或只是为了好玩),您可以使用 multiprocessing 尝试这个场景并比较一下结果。

抓取速度提升 3 倍!Python 的这个内置库你用上了吗?

顺序实现的版本

我们将从抓取 scrapeme.live 作为示例开始,这是一个专门用于测试的电子商务网站。

首先,我们将从顺序抓取的版本开始。以下几个片段是所有案例的一部分,因此它们将保持不变。

通过访问目标主页,我们发现它有 48 个子页面。由于是测试环境,这些子页面不会很快发生变化,我们会使用到以下两个常量:

base_url = "https://scrapeme.live/shop/page" 
pages = range(149# max page (48) + 1

现在,从目标产品中提取基础数据。为此,我们使用 requests.get 获取 HTML 内容,然后使用 BeautifulSoup 解析它。我们将遍历每个产品并从中获取一些基本信息。所有选择器都来自对内容的手动审查(使用 DevTools),但为简洁起见,我们不会在这里详细介绍。

import requests 
from bs4 import BeautifulSoup 
 
def extract_details(page): 
 # concatenate page number to base URL 
 response = requests.get(f"{base_url}/{page}/"
 soup = BeautifulSoup(response.text, "html.parser"
 
 pokemon_list = [] 
 for pokemon in soup.select(".product"): # loop each product 
  pokemon_list.append({ 
   "id": pokemon.find(class_="add_to_cart_button").get("data-product_id"), 
   "name": pokemon.find("h2").text.strip(), 
   "price": pokemon.find(class_="price").text.strip(), 
   "url": pokemon.find(class_="woocommerce-loop-product__link").get("href"), 
  }) 
 return pokemon_list

extract_details 函数将获取一个页码并将其连接起来,用于创建子页面的 URL。获取内容并创建产品数组后返回。这意味着返回的值将是一个字典列表,这是一个后续使用的必要细节。

我们需要为每个页面运行上面的函数,获取所有结果,并存储它们。

import csv 
 
# modified to avoid running all the pages unintentionally 
pages = range(13
 
def store_results(list_of_lists): 
 pokemon_list = sum(list_of_lists, []) # flatten lists 
 
 with open("pokemon.csv""w"as pokemon_file: 
  # get dictionary keys for the CSV header 
  fieldnames = pokemon_list[0].keys() 
  file_writer = csv.DictWriter(pokemon_file, fieldnames=fieldnames) 
  file_writer.writeheader() 
  file_writer.writerows(pokemon_list) 
 
list_of_lists = [ 
 extract_details(page) 
 for page in pages 

store_results(list_of_lists)

运行上面的代码将获得两个产品页面,提取产品(总共 32 个),并将它们存储在一个名为 pokemon.csv 的 CSV 文件中。 store_results 函数不影响顺序或并行模式下的抓取。你可以跳过它。

由于结果是列表,我们必须将它们展平以允许 writerows 完成其工作。这就是为什么我们将变量命名为list_of_lists(即使它有点奇怪),只是为了提醒大家它不是扁平的。

输出 CSV 文件的示例:

id name price url
759 Bulbasaur £63.00 https://scrapeme.live/shop/Bulbasaur/
729 Ivysaur £87.00 https://scrapeme.live/shop/Ivysaur/
730 Venusaur £105.00 https://scrapeme.live/shop/Venusaur/
731 Charmander £48.00 https://scrapeme.live/shop/Charmander/
732 Charmeleon £165.00 https://scrapeme.live/shop/Charmeleon/

如果您要为每个页面 (48) 运行脚本,它将生成一个包含 755 个产品的 CSV 文件,并花费大约 30 秒。

time python script.py 
 
real 0m31,806s 
user 0m1,936s 
sys 0m0,073s

asyncio 介绍

我们知道我们可以做得更好。如果我们同时执行所有请求,它应该花费更少时间,对吧?也许会和执行最慢的请求所花费的时间相等。

并发确实应该运行得更快,但它也涉及一些开销。所以这不是线性的数学改进。

为此,我们将使用上面提到的 asyncio。它允许我们在事件循环中的同一个线程上运行多个任务(就像 Javascript 一样)。它将运行一个函数,并在运行时允许时将上下文切换到不同的上下文。在我们的例子中,HTTP 请求允许这种切换。

我们将开始看到一个 sleep 一秒钟的示例。并且脚本应该需要一秒钟才能运行。请注意,我们不能直接调用 main。我们需要让 asyncio 知道它是一个需要执行的异步函数。

import asyncio 
 
async def main(): 
 print("Hello ..."
 await asyncio.sleep(1
 print("... World!"
 
asyncio.run(main())
time python script.py 
Hello ... 
... World! 
 
real 0m1,054s 
user 0m0,045s 
sys 0m0,008s

简单的并行代码

接下来,我们将扩展一个示例案例来运行一百个函数。它们每个都会 sleep 一秒钟并打印一个文本。如果我们按顺序运行它们大约需要一百秒。使用 asyncio,只需要一秒!

这就是并发背后的力量。如前所述,对于纯 I/O 密集型任务,它将执行得更快 – sleep 不是,但它对示例很重要。

我们需要创建一个辅助函数,它会 sleep 一秒钟并打印一条消息。然后,我们编辑 main 以调用该函数一百次,并将每个调用存储在一个任务列表中。最后也是关键的部分是执行并等待所有任务完成。这就是 asyncio.gather[4] 所做的事情。

import asyncio 
 
async def demo_function(i): 
 await asyncio.sleep(1
 print(f"Hello {i}"
 
async def main(): 
 tasks = [ 
  demo_function(i) 
  for i in range(0100
 ] 
 await asyncio.gather(*tasks) 
 
asyncio.run(main())

正如预期的那样,一百条消息和一秒钟的执行时间。完美!

使用 asyncio 进行抓取

我们需要将这些知识应用于数据抓取。遵循的方法是同时请求并返回产品列表,并在所有请求完成后存储它们。每次请求后或者分批保存数据可能会更好,以避免实际情况下的数据丢失。

我们的第一次尝试不会有并发限制,所以使用时要小心。在使用数千个 URL 运行它的情况下……好吧,它几乎会同时执行所有这些请求。这可能会给服务器带来巨大的负载,并可能会损害您的计算机。

requests 不支持开箱即用的异步,因此我们将使用 aiohttp [5] 来避免复杂化。 requests 可以完成这项工作,并且没有实质性的性能差异。但是使用 aiohttp 代码更具可读性。

import asyncio 
import aiohttp 
from bs4 import BeautifulSoup 
 
async def extract_details(page, session): 
 # similar to requests.get but with a different syntax 
 async with session.get(f"{base_url}/{page}/"as response: 
 
  # notice that we must await the .text() function 
  soup = BeautifulSoup(await response.text(), "html.parser"
 
  # [...] same as before 
  return pokemon_list 
 
async def main(): 
 # create an aiohttp session and pass it to each function execution 
 async with aiohttp.ClientSession() as session: 
  tasks = [ 
   extract_details(page, session) 
   for page in pages 
  ] 
  list_of_lists = await asyncio.gather(*tasks) 
  store_results(list_of_lists) 
 
asyncio.run(main())

CSV 文件应该像以前一样包含每个产品的信息 (共 755 个)。由于我们同时执行所有页面调用,结果不会按顺序到达。如果我们将结果添加到 extract_details 内的文件中,它们可能是无序的。但我们会等待所有任务完成然后处理它们,因此顺序性不会有太大影响。

time python script.py 
 
real 0m11,442s 
user 0m1,332s 
sys 0m0,060s

我们做到了!速度提升了 3 倍,但是……不应该是 40 倍吗?没那么简单。许多因素都会影响性能(网络、CPU、RAM 等)。

在这个演示页面中,我们注意到当执行多个调用时,响应时间会变慢,这可能是设计使然。一些服务器/提供商可以限制并发请求的数量,以避免来自同一 IP 的过多流量。它不是一种阻塞,而是一个队列。你会得到服务响应,但需要稍等片刻。

要查看真正的加速,您可以针对延迟[6]页面进行测试。这是另一个测试页面,它将等待 2 秒然后返回响应。

base_url = "https://httpbin.org/delay/2" 
#... 
 
async def extract_details(page, session): 
 async with session.get(base_url) as response: 
  #...

这里去掉了所有的提取和存储逻辑,只调用了延迟 URL 48 次,并在 3 秒内运行完毕。

time python script.py 
 
real 0m2,865s 
user 0m0,245s 
sys 0m0,031s

使用信号量限制并发

如上所述,我们应该限制并发请求的数量,尤其是针对单个域名。

asyncio 带有 Semaphore[7],一个将获取和释放锁的对象。它的内部功能将阻塞一些调用,直到获得锁,从而创建最大的并发性。

我们需要创建尽可能最大值的信号量。然后等待提取函数运行,直到 async with sem 可用。

max_concurrency = 3 
sem = asyncio.Semaphore(max_concurrency) 
 
async def extract_details(page, session): 
 async with sem: # semaphore limits num of simultaneous downloads 
  async with session.get(f"{base_url}/{page}/"as response: 
   # ... 
 
async def main(): 
  # ... 
 
loop = asyncio.get_event_loop() 
loop.run_until_complete(main())

它完成了工作,并且相对容易实现!这是最大并发设置为 3 的输出。

time python script.py 
 
real 0m13,062s 
user 0m1,455s 
sys 0m0,047s

这表明无限并发的版本并没有全速运行。如果我们将限制增加到 10,总时间与未限制的脚本运行时间相近。

使用 TCPConnector 限制并发

aiohttp 提供了一种替代解决方案,可提供进一步的配置。我们可以创建传入自定义 TCPConnector[8] 的客户端会话。

我们可以使用两个适合我们需求的参数来构建它:

  • limit – “同时连接的总数”。
  • limit_per_host – “限制同时连接到同一端点的连接数”(同一主机、端口和 is_ssl)。
max_concurrency = 10 
max_concurrency_per_host = 3 
 
async def main(): 
 connector = aiohttp.TCPConnector(limit=max_concurrency, limit_per_host=max_concurrency_per_host) 
 async with aiohttp.ClientSession(connector=connector) as session: 
  # ... 
 
asyncio.run(main())

这种写法也易于实施和维护!这是每个主机最大并发设置为 3 的输出。

time python script.py 
 
real 0m16,188s 
user 0m1,311s 
sys 0m0,065s

Semaphore 相比的优势是可以选择限制每个域的并发调用和请求的总量。我们可以使用同一个会话来抓取不同的站点,每个站点都有自己的限制。

缺点是它看起来有点慢。需要针对真实案例,使用更多页面和实际数据运行一些测试。

multiprocessing

就像我们之前看到的那样,数据抓取是 I/O 密集型的。但是,如果我们需要将它与一些 CPU 密集型计算混合怎么办?为了测试这种情况,我们将使用一个函数,该函数将在每个抓取的页面之后 count_a_lot。这是强制 CPU 忙碌一段时间的简单(且有些愚蠢)的方法。

def count_a_lot(): 
 count_to = 100_000_000 
 counter = 0 
 while counter < count_to: 
  counter = counter + 1 
 
async def extract_details(page, session): 
 async with session.get(f"{base_url}/{page}/"as response: 
  # ... 
  count_a_lot() 
  return pokemon_list

对于 asyncio 版本,只需像以前一样运行它。可能需要很长时间⏳。

time python script.py 
 
real 2m37,827s 
user 2m35,586s 
sys 0m0,244s

现在,比较难理解的部分来了:

直接引入 multiprocessing 看起来有点困难。实际上,我们需要创建一个 ProcessPoolExecutor,它能够“使用一个进程池来异步执行调用”。它将处理不同 CPU 中每个进程的创建和控制

但它不会分配负载。为此,我们将使用 NumPyarray_split,它会根据 CPU 的数量将页面范围分割成相等的块。

main 函数的其余部分类似于 asyncio 版本,但更改了一些语法以匹配 multiprocessing 的语法风格。

此处的本质区别是我们不会直接调用extract_details。实际上是可以的,但我们将尝试通过将 multiprocessingasyncio 混合使用来获得最好的执行效率。

from concurrent.futures import ProcessPoolExecutor 
from multiprocessing import cpu_count 
import numpy as np 
 
num_cores = cpu_count() # number of CPU cores 
 
def main(): 
 executor = ProcessPoolExecutor(max_workers=num_cores) 
 tasks = [ 
  executor.submit(asyncio_wrapper, pages_for_task) 
  for pages_for_task in np.array_split(pages, num_cores) 
 ] 
 doneTasks, _ = concurrent.futures.wait(tasks) 
 
 results = [ 
  item.result() 
  for item in doneTasks 
 ] 
 store_results(results) 
 
main()

长话短说,每个 CPU 进程都会有几页需要抓取。一共有 48 个页面,假设你的机器有 8 个 CPU,每个进程将请求 6 个页面(6 * 8 = 48)。

这六个页面将同时运行!之后,计算将不得不等待,因为它们是 CPU 密集型的。但是我们有很多 CPU,所以它们应该比纯 asyncio 版本运行得更快。

async def extract_details_task(pages_for_task): 
 async with aiohttp.ClientSession() as session: 
  tasks = [ 
   extract_details(page, session) 
   for page in pages_for_task 
  ] 
  list_of_lists = await asyncio.gather(*tasks) 
  return sum(list_of_lists, []) 
 
 
def asyncio_wrapper(pages_for_task): 
 return asyncio.run(extract_details_task(pages_for_task))

这就是神奇的地方。每个 CPU 进程将使用页面的子集启动一个 asyncio(例如,第一个页面从 1 到 6)。

然后,每一个都将调用几个 URL,使用已知的 extract_details 函数。

上述内容需要花点时间来吸收它。整个过程是这样的:

  1. 创建执行器
  2. 拆分页面
  3. 每个进程启动 asyncio
  4. 创建一个 aiohttp 会话并创建页面子集的任务
  5. 提取每一页的数据
  6. 合并并存储结果

下面是本次的执行时间。虽然之前我们没有提到它,但这里的 user 时间却很显眼。对于仅运行 asyncio 的脚本:

time python script.py 
 
real 2m37,827s 
user 2m35,586s 
sys 0m0,244s

具有 asyncio 和多个进程的版本:

time python script.py 
 
real 0m38,048s 
user 3m3,147s 
sys 0m0,532s

发现区别了吗?实际运行时间方面第一个用了两分钟多,第二个用了 40 秒。但是在总 CPU 时间(user 时间)中,第二个超过了三分钟!看起来系统开销的耗时确实有点多。

这表明并行处理“浪费”了更多时间,但程序是提前完成的。显然,您在决定选择哪种方法时,需要考虑到开发和调试的复杂度。

结论

我们已经看到 asyncio 足以用于抓取,因为大部分运行时间都用于网络请求,这种场景属于 I/O 密集型并且适用于单核中的并发处理。

如果收集的数据需要一些 CPU 密集型工作,这种情况就会改变。虽然有关计数的例子有一点愚蠢,但至少你理解了这种场景。

在大多数情况下,带有 aiohttpasyncio 比异步的 requests 更适合完成目标工作。同时我们可以添加自定义连接器以限制每个域名的请求数、并发请求总数。有了这三个部分,您就可以开始构建一个可以扩展的数据抓取程序了。

另一个重要的部分是允许新的 URL/任务加入程序运行(类似于队列),但这是另一篇文章的内容。敬请关注!

参考资料

[1]

 

python 3: https://www.python.org/downloads/

[2]

asyncio: https://docs.python.org/3/library/asyncio.html

[3]

multiprocessing: https://docs.python.org/3/library/multiprocessing.html

[4]

asyncio.gather: https://docs.python.org/3/library/asyncio-task.html#asyncio.gather

[5]

aiohttp: https://docs.aiohttp.org/

[6]

延迟: https://httpbin.org/delay/2

[7]

Semaphore: https://docs.python.org/3/library/asyncio-sync.html#asyncio.Semaphore

[8]

TCPConnector: https://docs.aiohttp.org/en/stable/client_reference.html#tcpconnector

[9]

参考原文: https://www.zenrows.com/blog/speed-up-web-scraping-with-concurrency-in-python

 

 

– EOF –

 

转自:https://mp.weixin.qq.com/s/pPpc7CH4Rr9WTa4p8ue2Iw

python-office库

1行代码,生成动漫头像

听说某宝需要50块钱一张?别再去交智商税了!

1. 安装python-office

安装很简单,在有python环境的电脑上,只需要执行下面这一行命令。

“ 如果你之前使用过python-office这个库,也需要执行一下,可以下载到最新版本~

安装

pip install -i https://pypi.tuna.tsinghua.edu.cn/simple python-office -U

如果你的电脑里还没有安装python环境,可以看一下下面这个6分钟的傻瓜式安装教程,有电脑就能操作~

作者:程序员晚枫
链接:https://zhuanlan.zhihu.com/p/531681488
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2. 生成动漫头像

直接上代码!

代码

# 导入这个库:python-office,简写为office
import office

# 1行代码,实现 人像转动漫头像
office.image.img2Cartoon(path = 'd://image//程序员晚枫.jpg')

# 参数说明:
# path:存放自己真人照片的位置 + PDF的文件名,例如:d://image//程序员晚枫.pdf

直接运行以上代码,就会得到一张转化后的动漫头像了。

“ 程序可能需要运行20秒左右。

3.全部功能

1行代码实现复杂功能,是不是使用起来很方便?

“ 项目已被收录进【开源中国】、【Python官网】等平台,所有功能,免费给大家使用:GitHub

4. python-office库,近期添加的功能

10个有趣的 Python 高级脚本,建议收藏!

来自公众号:法纳斯特

大家好,我是小F。

 

在日常的工作中,我们总会面临到各式各样的问题。

 

其中不少的问题,使用一些简单的Python代码就能解决。

 

比如不久前的复旦大佬,用130行Python代码硬核搞定核酸统计,大大提升了效率,节省了不少时间。

 

今天,小F就带大家学习一下10个Python脚本程序。

 

虽然简单,不过还是蛮有用的。

 

有兴趣的可以自己去实现,找到对自己有帮助的技巧。

 

 

▍1、Jpg转Png

 

图片格式转换,以前小F可能第一时间想到的是【格式工厂】这个软件。

 

如今编写一个Python脚本就能完成各种图片格式的转换,此处以jpg转成png为例。

 

有两种解决方法,都分享给大家。

 

# 图片格式转换, Jpg转Png

# 方法①
from PIL import Image

img = Image.open(‘test.jpg’)
img.save(‘test1.png’)


# 方法②
from cv2 import imread, imwrite

image = imread(“test.jpg”1)
imwrite(“test2.png”, image)

 

 

▍2、PDF加密和解密

 

如果你有100个或更多的PDF文件需要加密,手动进行加密肯定是不可行的,极其浪费时间。

 

使用Python的pikepdf模块,即可对文件进行加密,写一个循环就能进行批量加密文档。

 

# PDF加密
import pikepdf

pdf = pikepdf.open(“test.pdf”)
pdf.save(‘encrypt.pdf’, encryption=pikepdf.Encryption(owner=“your_password”, user=“your_password”, R=4))
pdf.close()

 

有加密那么便会有解密,代码如下。

# PDF解密
import pikepdf

pdf = pikepdf.open(“encrypt.pdf”,  password=‘your_password’)
pdf.save(“decrypt.pdf”)
pdf.close()

 

 

▍3、获取电脑的配置信息

 

很多小伙伴可能会使用鲁大师来看自己的电脑配置,这样还需要下载一个软件。

 

使用Python的WMI模块,便可以轻松查看你的电脑信息。

 

# 获取计算机信息
import wmi


def System_spec():
    Pc = wmi.WMI()
    os_info = Pc.Win32_OperatingSystem()[0]
    processor = Pc.Win32_Processor()[0]
    Gpu = Pc.Win32_VideoController()[0]
    os_name = os_info.Name.encode(‘utf-8’).split(b’|’)[0]
    ram = float(os_info.TotalVisibleMemorySize) / 1048576

    print(f’操作系统: {os_name})
    print(f’CPU: {processor.Name})
    print(f’内存: {ram} GB’)
    print(f’显卡: {Gpu.Name})

    print(“n计算机信息如上 ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑”)


System_spec()

 

就以小F自己的电脑为例,运行代码就能看到配置。

 

10个有趣的 Python 高级脚本,建议收藏!

 

 

▍4、解压文件

 

使用zipfile模块进行文件解压,同理也可以对文件进行压缩。

 

# 解压文件
from zipfile import ZipFile

unzip = ZipFile(“file.zip”“r”)
unzip.extractall(“output Folder”)

 

 

▍5、Excel工作表合并

 

帮助你将Excel工作表合并到一张表上,表内容如下图。

 

10个有趣的 Python 高级脚本,建议收藏!

 

6张表,其余表的内容和第一张表都一样。

 

设置表格数量为5,将会合并前5张表的内容。

 

import pandas as pd

# 文件名
filename = “test.xlsx”
# 表格数量
T_sheets = 5

df = []
for i in range(1, T_sheets+1):
    sheet_data = pd.read_excel(filename, sheet_name=i, header=None)
    df.append(sheet_data)

# 合并表格
output = “merged.xlsx”
df = pd.concat(df)
df.to_excel(output)

 

结果如下。

 

10个有趣的 Python 高级脚本,建议收藏!

 

 

▍6、将图像转换为素描图

 

和之前的图片格式转换有点类似,就是对图像进行处理。

 

以前大家可能会使用到美图秀秀,现在可能就是抖音的滤镜了。

 

其实使用Python的OpenCV,就能够快速实现很多你想要的效果。

 

# 图像转换
import cv2

# 读取图片
img = cv2.imread(“img.jpg”)
# 灰度
grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
invert = cv2.bitwise_not(grey)
# 高斯滤波
blur_img = cv2.GaussianBlur(invert, (77), 0)
inverse_blur = cv2.bitwise_not(blur_img)
sketch_img = cv2.divide(grey, inverse_blur, scale=256.0)
# 保存
cv2.imwrite(‘sketch.jpg’, sketch_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

原图如下。

 

10个有趣的 Python 高级脚本,建议收藏!

 

素描图如下,还挺好看的。

 

10个有趣的 Python 高级脚本,建议收藏!

 

 

▍7、获取CPU温度

 

有了这个Python脚本,你将不需要任何软件来了解CPU的温度。

 

# 获取CPU温度
from time import sleep
from pyspectator.processor import Cpu
cpu = Cpu(monitoring_latency=1)
with cpu:
    while True:
        print(f’Temp: {cpu.temperature} °C’)
        sleep(2)

 

 

▍8、提取PDF表格

 

有的时候,我们需要从PDF中提取表格数据。

 

第一时间你可能会先想到手工整理,但是当工作量特别大,手工可能就比较费劲。

 

然后你可能会想到一些软件和网络工具来提取 PDF 表格。

 

下面这个简单的脚本将帮助你在一秒钟内完成相同的操作。

 

# 方法①
import camelot

tables = camelot.read_pdf(“tables.pdf”)
print(tables)
tables.export(“extracted.csv”, f=“csv”, compress=True)

# 方法②, 需要安装Java8
import tabula

tabula.read_pdf(“tables.pdf”, pages=“all”)
tabula.convert_into(“table.pdf”“output.csv”, output_format=“csv”, pages=“all”)

 

PDF文档的内容如下,包含了一个表格。

 

10个有趣的 Python 高级脚本,建议收藏!

 

提取到的CSV文件内容如下。

 

10个有趣的 Python 高级脚本,建议收藏!

 

 

▍9、截图

 

该脚本将简单地截取屏幕截图,而无需使用任何屏幕截图软件。

 

在下面的代码中,给大家展示了两种Python截取屏幕截图的方法。

 

# 方法①
from mss import mss
with mss() as screenshot:
    screenshot.shot(output=‘scr.png’)

# 方法②
import PIL.ImageGrab
scr = PIL.ImageGrab.grab()
scr.save(“scr.png”)

 

 

▍10、拼写检查器

 

这个Python脚本可以进行拼写检查,当然只对英文有效,毕竟中文博大精深呐。

 

# 拼写检查
# 方法①
import textblob

text = “mussage”
print(“original text: “ + str(text))

checked = textblob.TextBlob(text)
print(“corrected text: “ + str(checked.correct()))

# 方法②
import autocorrect
spell = autocorrect.Speller(lang=‘en’)

# 以英语为例
print(spell(‘cmputr’))
print(spell(‘watr’))
print(spell(‘survice’))

— EOF —

转自:https://mp.weixin.qq.com/s/wuA5_ZV328E5bpSSR0XbiA

FastAPI的小兄弟,开发命令行工具更给力

来源丨经授权转自 未闻Code(ID:itskingname)

作者丨kingname

关注我公众号的同学都知道,我非常喜欢FastAPI这个web框架。它在易用性上面做到了极致,帮助开发者减少了很多不必要的工作。

FastAPI的开发组织叫做tiangolo,他家除了FastAPI外,还有另一个项目也非常好用,叫做typer

三年前,我写过一篇文章一日一技:快速实现Python 命令行参数介绍另一个命令行工具fire. 而typer做得比fire还要好。

首先使用pip来安装它:

python3 -m pip install typer

函数参数等于命令行参数

我们首先来看看typer怎么使用。创建一个example_1.py文件,写入如下代码。

import typer


def main(name: str, salary: int):
    print(f'{name}月薪{salary}元')


if __name__ == '__main__':
    typer.run(main)

直接运行,Python会报错:

FastAPI的小兄弟,开发命令行工具更给力

使用参数--help可以查看这个脚本的命令行参数:

FastAPI的小兄弟,开发命令行工具更给力

于是我们根据这里的提示,输入正确的参数,从而正常运行程序:

FastAPI的小兄弟,开发命令行工具更给力

子命令与自动补全更好用

假设我们有一个神经网络的程序,其中的入口函数代码如下:

def train_data(train_folder: str, test_folder: str, rate: float = 0.8):
    """
    训练人脸检测模型
    """
    print(f'使用文件夹{train_folder}中的数据进行训练')
    print(f'使用{test_folder}中的数据用来验证训练效果,确保准确率>{rate}')
    return True

def predict(folder: str):
    """
    使用训练好的模型预测
    """
    print(f'对文件夹{folder}中的数据进行预测。')

显然,这个程序可以用来训练数据,也可以用来预测数据,所以有两种不同的命令,每一种命令有不同的参数。

这种情况下,使用typer非常方便,只需要加两个装饰器就可以了:

FastAPI的小兄弟,开发命令行工具更给力

运行效果如下图所示:

FastAPI的小兄弟,开发命令行工具更给力

输入具体的子命令,还可以查看每个子命令的参数:

FastAPI的小兄弟,开发命令行工具更给力

因此,我可以使用两个不同的子命令来运行程序:

FastAPI的小兄弟,开发命令行工具更给力

你以为这样就完了?我们再安装它的一个辅助工具typer-cli,还可以做更多事情:

python3 -m pip install typer-cli
typer --install-completion

有了这个东西,我们运行程序可以这样写:

typer example_2.py run 子命令 参数1 参数2 --可选参数1 可选参数1的值

例如:

FastAPI的小兄弟,开发命令行工具更给力

并且,typer可以帮我们可以实现自动补全:

输入typer example_2.py run 然后按下Tab键,自动告诉你可以输入哪些子命令,如下图所示:

FastAPI的小兄弟,开发命令行工具更给力

除此之外,如果你的命令行程序只有一个命令,那么你甚至只需要写一个函数,连typer都不需要导入,就可以使用typer来运行:

FastAPI的小兄弟,开发命令行工具更给力

自动生成文档也简单

我们知道,FastAPI自动生成接口文档的功能非常好用。typer作为它的兄弟,也继承了这个高级功能。我们来看看:

import typer

app = typer.Typer(help="人脸检测模型")


@app.command()
def train_data(train_folder: str, test_folder: str, rate: float = 0.8):
    """
    训练人脸检测模型
    """
    print(f'使用文件夹{train_folder}中的数据进行训练')
    print(f'使用{test_folder}中的数据用来验证训练效果,确保准确率>{rate}')
    return True

@app.command()
def predict(folder: str):
    """
    使用训练好的模型预测
    """
    print(f'对文件夹{folder}中的数据进行预测。')

运行命令:

typer main.py utils docs --name "python3 main.py" --output readme.md

自动在当前文件夹生成一个readme.md文件。我们使用任何能够渲染Markdown的软件打开这个文档,可以看到文档内容如下:

FastAPI的小兄弟,开发命令行工具更给力

这样一来,我们不需要额外花心思去维护文档,只需要在修改完代码、增删新的命令或者参数以后,运行这个命令,就可以把文档自动更新。

转自:https://mp.weixin.qq.com/s/jE5cQtlmXXxJ9-eTpXsW-w