Почему зависает скрейпинг?

Я запускаю код Python для очистки веб-страниц, что означает, что код будет время от времени записывать (добавлять) данные в текстовый файл. Иногда код просто зависает, но в Shell не появляется сообщение об ошибке. Я хотел бы знать, является ли это более вероятным из-за того, что система raspbian ненадежна, или из-за того, что в моем коде есть некоторые скрытые проблемы.


person user3768495    schedule 18.02.2015    source источник
comment
Очень хорошее практическое правило заключается в том, что это всегда ваш код, почти в любой ситуации. Вдвойне, если зависает только один процесс. Почему бы вам не изучить процесс, чтобы сказать, что происходит?   -  person phihag    schedule 18.02.2015
comment
Спасибо, фихаг. Поскольку я недавно начал изучать Python, я не знаю, как «изучить процесс». Я имею в виду, что нет сообщений об ошибках или данных журнала, которые я мог бы просмотреть. Не могли бы вы предложить что-нибудь?   -  person user3768495    schedule 20.02.2015
comment
Я написал ответ с несколькими авеню. Как сказал @AGS, мы не можем помочь вам отследить проблему в вашей программе без фактического проверяемого кода.   -  person phihag    schedule 20.02.2015


Ответы (1)


очень хорошее практическое правило: ошибка всегда в вашем собственном коде, а не в системе. В частности, если остальная часть системы работает (т.е. вы можете использовать другую консоль), то почти наверняка в зависании виновата ваша программа.

В оболочке, в которой выполняется ваш процесс, попробуйте нажать Ctrl+C, чтобы остановить процесс, или Ctrl+\< /kbd> чтобы просто выйти из него. Вы должны получить сообщение об ошибке, показывающее, где находилась ваша программа, когда вы ее завершили. Предположим, что ваша программа

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

^C — это визуальное представление Ctrl+C, и его можно игнорировать. Остальное — это трассировка стека, показывающая, что делала программа, когда мы ее запускали. Для хорошего эффекта прервите свою программу несколько раз и сравните трассировку стека, чтобы увидеть, что она обычно делает, когда «зависает».

Кроме того, расширьте свою программу дополнительными выводами отладки (возможно, переключаемыми новым переключателем), чтобы вам не нужно было прерывать ее в первую очередь. Хорошая идея — всегда выводить что-то до и после подключения к сети.

Поскольку вы находитесь в сети, другая сторона также может просто молча умереть, и ваша программа может подождать некоторое время, чтобы подтвердить, что это произошло (в отличие от просто замедления из-за плохого приема или высокой перегрузки в сети). Вы можете вызвать socket.setdefaulttimeout с небольшим значением, чтобы ваша программа выйти пораньше, вместо того, чтобы ждать, пока другая сторона что-то скажет.

Вы также можете использовать различные инструменты для облегчения отладки. Например, введите htop (sudo apt-get install -y htop один раз, чтобы установить его, если вы еще этого не сделали, альтернативно top тоже работает), чтобы увидеть, как продвигается ваша программа. Посмотрите на коэффициент загрузки процессора (в самом верху) и где указана ваша программа.

Скажем, это выглядит так:

ЦП ||| 8,0%

Несмотря на сортировку по ЦП (нажмите F6 для сортировки), наша программа здесь даже не отображается, а htop в любом случае является единственной программой, использующей большую часть ЦП. Это означает, что наша программа (если она запущена) застряла в системном вызове, т.е. управление операционной системой. Но поскольку операционная система также не использует много ресурсов ЦП (его использование ЦП показано красным), похоже, мы чего-то ждем!

С другой стороны, вывод htop выглядит так:

ЦП ||||||||||||||| 100%

Вы заметите, что 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, чтобы вы могли просмотреть его в другой оболочке, например, в текстовом редакторе.

Мы видим, что 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/stackoverflow/program2.py(1)<module>()
-> import socket
(Pdb) next
> /home/phihag/tmp/stackoverflow/program2.py(3)<module>()
-> c = socket.create_connection(('example.net', 80))
(Pdb) n
> /home/phihag/tmp/stackoverflow/program2.py(4)<module>()
-> while True:
(Pdb) n
> /home/phihag/tmp/stackoverflow/program2.py(5)<module>()
-> print(c.recv(1024))
(Pdb) n

Используйте next и step (или короткие n и s) для пошагового выполнения программы (для больших программ вам, скорее всего, понадобится continue и точка останова). Введите help pdb, чтобы просмотреть справочную информацию pdb. В этом случае мы видим, что строка, на которой в данный момент висит программа, — это строка 5 (print(c.recv(1024))).

Теперь, если вы не понимаете, почему ваша программа зависает, приведенные выше инструменты отладки должны предоставить вам достаточно информации для создания минимальный, полный, проверяемый пример.

Убедившись, что это также зависает, не стесняйтесь задать вопрос об этом в stackoverflow.

person phihag    schedule 20.02.2015