Requests: يقوم Response.text بإرجاع نص تم فك تشفيره بشكل غير صحيح (يطلب 1.2.3 ، Python 2.7)

تم إنشاؤها على ١٥ سبتمبر ٢٠١٣  ·  24تعليقات  ·  مصدر: psf/requests

إذا قام خادم http بإرجاع نوع المحتوى: text / * بدون ترميز ، فإن Response.text تقوم دائمًا بفك ترميزها على أنها نص "ISO-8859-1".

قد يكون صالحًا في RFC2616 / 3.7.1 ، لكن هذا خطأ في الحياة الواقعية في عام 2013.

لقد صنعت نموذجًا لصفحة بنص صيني:
http://lavr.github.io/python-emails/tests/requests/some-utf8-text.html
جميع المتصفحات تجعل هذه الصفحة بشكل صحيح.
لكن reguests.get يقوم بإرجاع نص غير صالح.

وإليك اختبار بسيط باستخدام عنوان url هذا:
https://gist.github.com/lavr/6572927

التعليق الأكثر فائدة

lavr (/ cc @ sigmavirus24) ، أسهل من ذلك ، يمكنك ببساطة توفير التشفير بنفسك.

>>> r = requests.get('http://irresponsible-server/')
>>> r.encoding = 'utf-8'

ثم تابع بشكل طبيعي.

ال 24 كومينتر

شكرا لهذا lavr!

هذا قرار تصميم متعمد للطلبات. نحن نتبع المواصفات ما لم نجد أنفسنا في وضع تختلف فيه المواصفات بشكل كبير عن سلوك العالم الحقيقي بحيث تصبح مشكلة (على سبيل المثال ، احصل على استجابة بعد 302 من POST).

إذا كان الخادم الرئيسي يعرف ما هو الترميز الصحيح ، فيجب أن يشير إليه. خلاف ذلك ، سوف نتبع ما تقوله المواصفات. =)

إذا كنت تعتقد أن المواصفات الافتراضية سيئة ، فأنا أشجعك بشدة على المشاركة في عملية RFC لـ HTTP / 2.0 من أجل تغيير هذا الإعداد الافتراضي. =)

ما قالهLukasa + حقيقة أنه إذا كان الترميز الذي تم استرداده من الرؤوس غير موجود ، فإننا نعتمد على التمثيلية للتخمين عند الترميز. مع عدد قليل جدًا من الأحرف ، لن تُرجع التمثيلية أي شيء نهائي لأنها تستخدم بيانات إحصائية لتخمين الترميز الصحيح.

بصراحة ، العام لا يحدث فرقا ولا يغير المواصفات أيضا.

إذا كنت تعرف الترميز الذي تتوقعه ، فيمكنك أيضًا القيام بفك التشفير بنفسك كما يلي:

text = str(r.content, '<ENCODING>', errors='replace')

لا حرج في الطلبات بقدر ما أشعر بالقلق وهذا ليس خطأ في التمثيلية أيضًا. نظرًا لأن Lukasa يبدو أنه يتفق معي ، فأنا أغلق هذا.

lavr (/ cc @ sigmavirus24) ، أسهل من ذلك ، يمكنك ببساطة توفير التشفير بنفسك.

>>> r = requests.get('http://irresponsible-server/')
>>> r.encoding = 'utf-8'

ثم تابع بشكل طبيعي.

@ kennethreitz هذا مخيب للآمال. لماذا نجعل ذلك سهلاً على الناس؟ = ص

على الاطلاق :)

في الغالب للمواقع اليابانية. كلهم يكذبون بشأن ترميزهم.

@ sigmavirus24
يرجى ملاحظة أن uses.get_encoding_from_headers تُرجع دائمًا "ISO-8859-1" ، وليس هناك فرصة لاستدعاء التمثيلية.
الخطأ هو: نتوقع أن يتم استخدام التمثيلية لتخمين الترميز ، لكنها ليست كذلك.

يعمل التصحيح أعلاه على إصلاح الخلل ، ولكنه لا يزال يتبع RFC.
من فضلك ، فكر في مراجعته.

lavr معذرة ، لم نوضح ذلك تمامًا. لا نتوقع أن يتم استدعاء التمثيلية في هذه الحالة. إن RFC واضح للغاية: إذا لم تحدد مجموعة أحرف ، وكان نوع MIME text/* ، فيجب افتراض أن الترميز هو ISO-8859-1. هذا يعني "لا تخمن". =)

lavr : فقط r.encoding إلى None ، وسيعمل كما تتوقع (على ما أظن).

أو افعل r.encoding = r.apparent_encoding .

حتى أفضل.

في r.encoding = None و r.encoding = r.apparent_encoding فقدنا معلومات مجموعة أحرف الخادم.
أعتقد أن تجاهل رأس الخادم تمامًا ليس حلاً جيدًا.

الحل الصحيح هو شيء من هذا القبيل:

r = requests.get(...)
params = cgi.parse_header(r.headers.get('content-type'))[0]
server_encoding = ('charset' in params) and params['charset'].strip("'\"") or None
r.encoding = server_encoding or r.apparent_encoding
text = r.text

تبدو غريبة :(

أو افعل هذا:

r = requests.get(...)

if r.encoding is None or r.encoding == 'ISO-8859-1':
    r.encoding = r.apparent_encoding

لا أعتقد ذلك :)

الشرط r.encoding is None لا معنى له ، لأن r.encoding لا يمكن أبدًا أن يكون بلا لنوع المحتوى = text / *.

r.encoding == 'ISO-8859-1' ... ماذا يعني ذلك؟ أرسل الخادم charset = 'ISO-8859-1' أم أن الخادم لم يرسل مجموعة أحرف؟ If first، I shouldn't guess charset. إذا أولاً ، لا يجب أن أخمن الأحرف.

lavr كنت أغطي القواعد غير النصية. يمكنك استبعاد احتمال charset باستخدام هذا الشرط بدلاً من ذلك:

r.encoding == 'ISO-8859-1' and not 'ISO-8859-1' in r.headers.get('Content-Type', '')

تضمين التغريدة
حسنًا ، يمكنني استخدام هذا الاختراق.
ويمكن للجميع في أوروبا الشرقية وآسيا استخدامه.

ولكن ماذا لو أصلحناه في الطلبات؟ ؛)
ماذا لو تمكنت الطلبات من تعيين enconding=None بأمانة عند الاستجابة بدون مجموعة أحرف؟

كما ناقشنا عدة مرات ، تتبع الطلبات مواصفات HTTP حرفياً. السلوك الحالي ليس خطأ. =)

حقيقة أنها ليست مفيدة لحالة الاستخدام الخاصة بك هي قصة أخرى كاملة. =)

حسنًا ، هذا نقاش كافٍ حول هذا. شكرا على ملاحظاتك.

تم تحديث HTTP 1.1 مع مجموعة أحرف افتراضية ISO-8859-1: http://tools.ietf.org/html/rfc7231#appendix -B

نحن بالفعل نتتبع هذا في # 2086. =)

لمن يهمه الأمر ، يوجد هنا تصحيح التوافق

قم بإنشاء ملف requests_patch.py بالكود التالي وقم باستيراده ، ثم يجب حل المشكلة.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import chardet

def monkey_patch():
    prop = requests.models.Response.content
    def content(self):
        _content = prop.fget(self)
        if self.encoding == 'ISO-8859-1':
            encodings = requests.utils.get_encodings_from_content(_content)
            if encodings:
                self.encoding = encodings[0]
            else:
                self.encoding = chardet.detect(_content)['encoding']

            if self.encoding:
                _content = _content.decode(self.encoding, 'replace').encode('utf8', 'replace')
                self._content = _content
                self.encoding = 'utf8'

        return _content
    requests.models.Response.content = property(content)

monkey_patch()


lavr (/ cc @ sigmavirus24) ، أسهل من ذلك ، يمكنك ببساطة توفير التشفير بنفسك.

>>> r = requests.get('http://irresponsible-server/')
>>> r.encoding = 'utf-8'

ثم تابع بشكل طبيعي.

شكرا على هذا! أي فكرة عن كيفية القيام بذلك في سطر واحد؟

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات