Почему csv.reader с TextIOWrapper включает символы новой строки?

У меня есть две функции: одна загружает отдельные файлы CSV, а другая загружает zip-архив с несколькими файлами CSV.

Функция download_and_process_csv корректно работает с response.iter_lines(), которая, похоже, удаляет символы новой строки.

«Курица, вода, кукурузная мука, соль, декстроза, сахар, фосфат натрия, эриторбат натрия, нитрит натрия. Произведено на предприятии, где присутствуют аллергены, такие как яйца, молоко, соя, пшеница, горчица, глютен, овес, молочные продукты».

Функция download_and_process_zip, похоже, по какой-то причине включает символы новой строки (\n\n). Я пробовал newline='' в io.TextIOWrapper, но он просто заменяет его на \r\n.

«Курица, вода, кукурузная мука, соль, декстроза, сахар, фосфат натрия, эриторбат натрия, нитрит натрия. \n\nПроизведено на предприятии, где присутствуют аллергены, такие как яйца, молоко, соя, пшеница, горчица, глютен, овес, молочные продукты».

Есть ли способ изменить download_and_process_zip так, чтобы символы новой строки были исключены/заменены, или мне придется перебирать все строки и вручную заменять символы?

@request_exceptions
def download_and_process_csv(client, url, model_class):
    with closing(client.get(url, stream=True)) as response:
        response.raise_for_status()
        response.encoding = 'utf-8'
        reader = csv.reader(response.iter_lines(decode_unicode=True))
        process_copy_from_csv(model_class, reader)


@request_exceptions
def download_and_process_zip(client, url):
    with closing(client.get(url, stream=True)) as response:
        response.raise_for_status()

        with io.BytesIO(response.content) as buffer:
            with zipfile.ZipFile(buffer, 'r') as z:
                for filename in z.namelist():
                    base_filename, file_extension = os.path.splitext(filename)
                    model_class = apps.get_model(base_filename)
                    if file_extension == '.csv':
                        with z.open(filename) as csv_file:
                            reader = csv.reader(io.TextIOWrapper(
                                csv_file,
                                encoding='utf-8',
                                # newline='',
                            ))
                            process_copy_from_csv(model_class, reader)

🤔 А знаете ли вы, что...
Python подходит для начинающих программистов благодаря своей простоте и читаемости кода.


4
74
1

Ответ:

Решено

Я поигрался с фиктивным сервером, который обслуживает этот CSV-файл:

"foo
bar"

CSV содержит одно поле "foo\nbar" в одной строке. Я называю новую строку в данных встроенной новой строкой.

Когда я использую метод iter_content для объекта Response:

print("Getting CSV")
resp = requests.get("http://localhost:8999/csv")
x = resp.iter_content(decode_unicode=True)

reader = csv.reader(x)
for row in reader:
    print(row)

Я получаю правильный вывод: выводится одна строка с одним полем данных:

Getting CSV
['foo\nbar']

Если я изменю iter_content на iter_lines, я получу неправильный результат:

Getting CSV
['foobar']

Судя по названию, я подозреваю, что iter_lines ищет любую последовательность символов, подобную новой строке, и останавливается на ней, прежде чем передать строку программе чтения csv (без новой строки), и поэтому встроенная новая строка эффективно удаляется. Я не могу говорить о вашем результате, когда новая строка была заменена пробелом... замены не происходит, просто эффективно удаляется.

Этот популярный SO, Использовать запросы Python для загрузки CSV, задает общий вопрос о загрузке CSV с помощью модуля запросов, но каждый ответ, кажется, адаптирован к тому факту, что рассматриваемый CSV не содержит встроенных символов новой строки, и поэтому там много ответов с iter_lines. Я не знаю, когда в запросы была добавлена ​​функция iter_content(), но об этом не упоминается ни в одном ответе.