为什么刮会冻结?

Why does scraping freeze?

我是运行一个python代码来做一些网络抓取,这意味着代码偶尔会写入(追加)数据到文本文件。有时代码会冻结,但 Shell 中不会显示任何错误消息。 我想知道这更有可能是因为 raspbian 系统不可靠还是因为我的代码有一些隐藏的问题。

一个very good rule of thumb是错误总是在你自己的代码而不是在系统。特别是,如果系统的其余部分是 运行ning(即您可以使用另一个控制台),那么挂起几乎可以肯定是您的程序的错误。

在 shell 中 运行ning 你的进程,尝试按 Ctrl+C 来询问您的进程停止,或 Ctrl+\ 完全退出。您应该会收到一条错误消息,显示您的程序在您终止时所在的位置。假设您的程序是

x = 0
def bar():
    return x * 2
def foo():
    x = 11
    while bar() < 100:
        x += 1

foo()

(你能发现错误吗?)我现在 运行 将其与 python program.py 连接,但它挂起并且没有终止。按 Ctrl+C 得到:

^CTraceback (most recent call last):
  File "python.py", line 9, in <module>
    foo()
  File "python.py", line 6, in foo
    while bar() < 100:
  File "python.py", line 3, in bar
    return x * 2
KeyboardInterrupt

^CCtrl+C的直观表示,可以忽略。其余的是一个 stack trace,它显示了当我们 运行 运行它时程序做了什么。为了获得良好的效果,请多次中断您的程序并比较堆栈跟踪以查看它在 "hanging".

时通常在做什么

此外,使用更多调试输出(可能由新开关切换)扩展您的程序,这样您就不需要首先中断它。一个好主意是在连接到网络之前和之后始终输出一些内容。

由于你在网络上,另一端也可能静静地死掉,你的程序可能会等待一段时间来确认发生了什么(而不是因为接收不良或网络高度拥塞而导致速度变慢)网络)。你可以用一个低值调用socket.setdefaulttimeout,让你的程序提前退出,而不是等待对方说些什么。

您还可以使用各种工具来辅助调试。例如,键入 htopsudo apt-get install -y htop 一次安装,如果您还没有安装它,或者 top 也可以)以查看您的程序的进展情况。查看 CPU 负载因子(在最顶部)和列出程序的位置。

说它看起来像这样:

尽管按 CPU 排序(按 F6 排序),我们的程序甚至没有出现在这里,并且 htop 是唯一使用大量 CPU 无论如何。这意味着我们的程序(如果它是 运行ning)卡在 system call 中,即已将控制权交给操作系统。但是由于操作系统也没有使用太多 CPU(它的 CPU 使用被映射为红色),看起来我们正在等待什么!

另一方面,htop 输出我的样子:

您会注意到 program.py 功能突出。这也不是因为我们的程序以任何方式强调操作系统,因为栏基本上都是绿色的!

那么您可能想要调查程序的当前状态,而不是仅仅查看聚合值或终止它。工具很多,但让我们看两个:

strace 实用程序(同样,使用 sudo apt-get install -y strace 安装一次)可以显示程序进行的系统调用。这适用于 每个 程序,而不仅仅是 Python 程序。 运行 它在我们的简单示例程序中产生:

$ strace -o log python program.py 
execve("/usr/bin/python", ["python", "program.py"], [/* 47 vars */]) = 0
brk(0)                                  = 0x218f000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fbb72742000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
* snip about 1000 lines*
read(3, "x = 0\ndef bar():\n    return x * "..., 4096) = 101
lseek(3, 101, SEEK_SET)                 = 101
brk(0x291d000)                          = 0x291d000
read(3, "", 4096)                       = 0
brk(0x2914000)                          = 0x2914000
close(3)                                = 0
munmap(0x7f7b9d294000, 4096)            = 0

如果你愿意,你也可以 运行 它作为 strace -o logfile python program.py 将输出写入 ./logfile 以便你可以在另一个 shell 中检查它,例如使用文本编辑器。

我们在这里看到 Python 需要 很多 系统调用才刚刚开始,但我们的程序根本不进行系统调用!我们看到,因为最后的系统调用是 Python 读取源代码文件。使用不同的程序,输出可能看起来像

socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("93.184.216.34")}, 16) = 0
recvfrom(3, 

重要的是最后一行,未完成,表明此时由操作系统控制。操作系统在做什么?好吧,它正在调用 recvfrom,或者正在等待数据。由于它只是在等待,实际上并没有做太多事情,因此 htop 只会显示可忽略不计的红色条。如果操作系统有错误,htop 现在会显示很多红色。

现在,我们如何知道哪个系统调用来自哪个 Python 语句?为此,我们需要一个 Python 调试器。您的 IDE 可能有一个集成的,但在紧要关头,内置的 pdb 可以工作。让我们 运行 它在我们的(新)程序中:

$ pdb program2.py 
> /home/phihag/tmp/Whosebug/program2.py(1)<module>()
-> import socket
(Pdb) next
> /home/phihag/tmp/Whosebug/program2.py(3)<module>()
-> c = socket.create_connection(('example.net', 80))
(Pdb) n
> /home/phihag/tmp/Whosebug/program2.py(4)<module>()
-> while True:
(Pdb) n
> /home/phihag/tmp/Whosebug/program2.py(5)<module>()
-> print(c.recv(1024))
(Pdb) n

使用nextstep(或简称ns)单步执行程序(对于大型程序,您很可能需要continue 和一个断点)。键入 help pdb 以查看 pdb 帮助信息。在这种情况下,我们看到程序当前挂起的行是第 5 行(print(c.recv(1024)))。

现在,如果您不明白为什么您的程序挂起,上述调试工具应该为您提供大量信息来创建一个minimal, complete, verifiable example

一旦你确认那个也挂了,请随意ask a Whosebug question