Запросы Python: использование response.raw в качестве стандартного ввода подпроцесса - PullRequest
0 голосов
/ 06 мая 2018

Я просто хочу использовать requests.get() для загрузки какого-либо файла (он может быть очень большим), а затем передать данные в stdin другого процесса, созданного subprocess.Popen. Пример кода:

In [137]: r = requests.get('http://www.google.com', stream=True)
In [138]: p = subprocess.Popen(['wc'], stdin=r.raw, stdout=subprocess.PIPE)
In [139]: p.communicate()

Это не работает хорошо. Две проблемы:

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

    $ sudo strace -p 181082                                                                                                             
    strace: Process 181082 attached
    read(0, "", 16384)                      = 0   <== Here, it takes very long time.
    fstat(1, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
    write(1, "      0       0       0\n", 24) = 24
    close(0)                                = 0
    close(1)                                = 0
    close(2)                                = 0
    exit_group(0)                           = ?
    +++ exited with 0 +++
    
  2. Данные, переданные на stdin, неверны. Как видите, вывод wc равен 0 0 0.

Я пытался установить r.raw.decode_content = True, но это не помогает.

ПРИМЕЧАНИЕ. Файл, загружаемый get, может быть очень большим, поэтому использование r.content и т. Д. Недопустимо.

ПРИМЕЧАНИЕ: я использую Python 2.7.

1 Ответ

0 голосов
/ 06 мая 2018

Самый простой способ - просто использовать response.iter_content , чтобы постепенно прочитать тело ответа и записать его в стандартный поток процесса в виде кусков:

import requests
import subprocess
r = requests.get('http://www.stackoverflow.com', stream=True)
r.raise_for_status()
p = subprocess.Popen(['wc'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
for chunk in r.iter_content(2048):
    p.stdin.write(chunk)
stdout, stderr = p.communicate()
print("wc output:", stdout)

Таким образом, вы не используете файлоподобный объект из requests, но он вам не нужен. Обратите внимание, что Popen уже создает файловый объект (а именно канал), доступный как process.stdin, который вы можете использовать для передачи данных процессу в реальном времени по мере его поступления.

Не сразу очевидно, что p.communicate() здесь делает две вещи:

  • закрывает стандартный входной канал, не записывая в него больше данных, сообщая wc, что мы закончили с записью, и он может вывести значения;
  • затем читает все данные из канала stdout в переменную.

Примечание: wc хорошо подходит здесь, поскольку потребляет все stdin до печати до stdout, но этот подход может зайти в тупик, если ваш процесс попытается записать в stdout до stdin полностью потребляется. В этом случае программа может зависнуть на p.stdin.write, в то время как процесс ожидает чтения Python из p.stdout.

Для того, чтобы справиться с этим правильно, вам нужно написать отдельные темы для чтения и записи. communicate() делает это за вас, но принимает входные данные только в виде строки, а не потока или генератора. Другой альтернативой может быть запись процесса в временный файл, а не в канал.

...