Поиск по блогу

понедельник, 11 августа 2014 г.

Как работать в Scrapy shell через прокси. Поски привели к request.meta Последующие эксперименты с TOR озадачили

Объект shell request - обертка для реклизации класса Request. Пример работы с объектом из оболочки здесь Problem logging into Facebook with Scrapy, а в этом посте мои эксперименты с request response, settings... Пост кончается тем, что я соединился с проверочным сервисом TOR (через Privoxy - TOR), а он мне сказал SORRY...
In []:
Ниже два фрагмента из "Problem logging into..."
In []:
header_vals={'Accept-Language': ['en'], 'Content-Type': ['application/ 
x-www-form-urlencoded'], 'Accept-Encoding': ['gzip,deflate'], 
'Accept': ['text/html,application/xhtml+xml,application/xml;q=0.9,*/ 
*;q=0.8'], 'User-Agent': ['Mozilla/5.0 Gecko/20070219 Firefox/ 
2.0.0.2']}

login_request=Request('https://www.facebook.com/login.php',headers=header_vals) 

fetch(login_request) 
In []:
new_request=FormRequest.from_response(response,formname='login_form',formdata={'email':'...@email.com','pass':'password'},headers=header_vals)

new_request.meta['download_timeout']=180 

new_request.meta['redirect_ttl']=30 
Могу ли я импортировать в shell объект Request... сейчас вместо него response... Сначала почитаем раздел долкументации и найдем у себя этот объект Requests and Responses
In [1]:
!dir C:\Users\kiss\Anaconda\Lib\site-packages\scrapy\http\request
 Volume in drive C has no label.
 Volume Serial Number is 6017-2A0B

 Directory of C:\Users\kiss\Anaconda\Lib\site-packages\scrapy\http\request

09.12.2013  15:39    <DIR>          .
09.12.2013  15:39    <DIR>          ..
09.12.2013  15:38             6В 176 form.py
09.12.2013  15:39             6В 421 form.pyc
09.12.2013  15:38             1В 077 rpc.py
09.12.2013  15:39             1В 619 rpc.pyc
09.12.2013  15:38             3В 471 __init__.py
09.12.2013  15:39             4В 819 __init__.pyc
               6 File(s)         23В 583 bytes
               2 Dir(s)  397В 833В 818В 112 bytes free

In [2]:
%load C:\\Users\\kiss\\Anaconda\\Lib\\site-packages\\scrapy\\http\\request\\__init__.py
In []:
"""
This module implements the Request class which is used to represent HTTP
requests in Scrapy.

See documentation in docs/topics/request-response.rst
"""

import copy

from w3lib.url import safe_url_string

from scrapy.http.headers import Headers
from scrapy.utils.trackref import object_ref
from scrapy.utils.decorator import deprecated
from scrapy.utils.url import escape_ajax
from scrapy.http.common import obsolete_setter

class Request(object_ref):

    def __init__(self, url, callback=None, method='GET', headers=None, body=None, 
                 cookies=None, meta=None, encoding='utf-8', priority=0,
                 dont_filter=False, errback=None):

        self._encoding = encoding  # this one has to be set first
        self.method = str(method).upper()
        self._set_url(url)
        self._set_body(body)
        assert isinstance(priority, int), "Request priority not an integer: %r" % priority
        self.priority = priority

        assert callback or not errback, "Cannot use errback without a callback"
        self.callback = callback
        self.errback = errback

        self.cookies = cookies or {}
        self.headers = Headers(headers or {}, encoding=encoding)
        self.dont_filter = dont_filter

        self._meta = dict(meta) if meta else None

    @property
    def meta(self):
        if self._meta is None:
            self._meta = {}
        return self._meta

    def _get_url(self):
        return self._url

    def _set_url(self, url):
        if isinstance(url, str):
            self._url = escape_ajax(safe_url_string(url))
        elif isinstance(url, unicode):
            if self.encoding is None:
                raise TypeError('Cannot convert unicode url - %s has no encoding' %
                    type(self).__name__)
            self._set_url(url.encode(self.encoding))
        else:
            raise TypeError('Request url must be str or unicode, got %s:' % type(url).__name__)
        if ':' not in self._url:
            raise ValueError('Missing scheme in request url: %s' % self._url)

    url = property(_get_url, obsolete_setter(_set_url, 'url'))

    def _get_body(self):
        return self._body

    def _set_body(self, body):
        if isinstance(body, str):
            self._body = body
        elif isinstance(body, unicode):
            if self.encoding is None:
                raise TypeError('Cannot convert unicode body - %s has no encoding' %
                    type(self).__name__)
            self._body = body.encode(self.encoding)
        elif body is None:
            self._body = ''
        else:
            raise TypeError("Request body must either str or unicode. Got: '%s'" % type(body).__name__)

    body = property(_get_body, obsolete_setter(_set_body, 'body'))

    @property
    def encoding(self):
        return self._encoding

    def __str__(self):
        return "<%s %s>" % (self.method, self.url)

    __repr__ = __str__

    def copy(self):
        """Return a copy of this Request"""
        return self.replace()

    def replace(self, *args, **kwargs):
        """Create a new Request with the same attributes except for those
        given new values.
        """
        for x in ['url', 'method', 'headers', 'body', 'cookies', 'meta', \
                'encoding', 'priority', 'dont_filter', 'callback', 'errback']:
            kwargs.setdefault(x, getattr(self, x))
        cls = kwargs.pop('cls', self.__class__)
        return cls(*args, **kwargs)

Заменить заголовки можнр легко, а вот с meta (здесь нас интересует прокси) сложнее, они только для просмотра

meta A dict that contains arbitrary metadata for this request. This dict is empty for new Requests, and is usually populated by different Scrapy components (extensions, middlewares, etc). So the data contained in this dict depends on the extensions you have enabled.
See Request.meta special keys for a list of special meta keys recognized by Scrapy.
This dict is shallow copied when the request is cloned using the copy() or replace() methods, and can also be accessed, in your spider, from the response.meta attribute
In []:
In [44]: request.meta
Out[44]:
{'depth': 0,
 'download_latency': 0.5850000381469727,
 'download_slot': 'www.dmoz.org',
 'download_timeout': 170,
 'handle_httpstatus_all': True}
In []:
In [45]: response.meta
Out[45]:
{'depth': 0,
 'download_latency': 0.5850000381469727,
 'download_slot': 'www.dmoz.org',
 'download_timeout': 170,
 'handle_httpstatus_all': True}
In []:
In [45]: response.meta.
response.meta.clear      response.meta.has_key    response.meta.itervalues response.meta.setdefault response.meta.viewkeys
response.meta.copy       response.meta.items      response.meta.keys       response.meta.update     response.meta.viewvalues
response.meta.fromkeys   response.meta.iteritems  response.meta.pop        response.meta.values
response.meta.get        response.meta.iterkeys   response.meta.popitem    response.meta.viewitems

Переприсвоить headers просто (повторяем пример)

In []:
In [38]: header_vals={'Accept-Language': ['en'], 'Content-Type': ['application x-www-form-urlencoded'], 'Accept-Encoding': ['g
flate'], 'Accept': ['text/html,application/xhtml+xml,application/xml;q=0.9,* *;q=0.8'], 'User-Agent': ['Mozilla/5.0 Gecko/2007
irefox 2.0.0.2']}
In []:
In [42]: request.headers=header_vals

In [43]: request.headers
Out[43]:
{'Accept': ['text/html,application/xhtml+xml,application/xml;q=0.9,* *;q=0.8'],
 'Accept-Encoding': ['gzip,deflate'],
 'Accept-Language': ['en'],
 'Content-Type': ['application x-www-form-urlencoded'],
 'User-Agent': ['Mozilla/5.0 Gecko/20070219 Firefox 2.0.0.2']}

А как поменять meta ???

In []:
In [45]: response.meta
Out[45]:
{'depth': 0,
 'download_latency': 0.5850000381469727,
 'download_slot': 'www.dmoz.org',
 'download_timeout': 170,
 'handle_httpstatus_all': True}

In [46]: response.meta['download_timeout']=175

In [47]: response.meta
Out[47]:
{'depth': 0,
 'download_latency': 0.5850000381469727,
 'download_slot': 'www.dmoz.org',
 'download_timeout': 175,
 'handle_httpstatus_all': True}
Если словарь сформирован, то можно обратится по ключу к элементу и поменять, как мы и сделали... А можно ли добавить новый элемент в словарь? Пока не нахожу вариантов. Посмотрим request, что делают эти функции... Некоторые с параметрами, их надо вызывать с ()
In []:
In [52]: request.meta.
request.meta.clear      request.meta.has_key    request.meta.itervalues request.meta.setdefault request.meta.viewkeys
request.meta.copy       request.meta.items      request.meta.keys       request.meta.update     request.meta.viewvalues
request.meta.fromkeys   request.meta.iteritems  request.meta.pop        request.meta.values
request.meta.get        request.meta.iterkeys   request.meta.popitem    request.meta.viewitems

In [52]: request.meta.items
Out[52]: <function items>

In [53]: request.meta.items()
Out[53]:
[('download_timeout', 175),
 ('handle_httpstatus_all', True),
 ('download_latency', 0.5850000381469727),
 ('depth', 0),
 ('download_slot', 'www.dmoz.org')]
Ага, получили не словарь, а список кортежей... а эта перебирает список кортежей при каждом вызове - следующий
In []:
In [59]: request.meta.popitem()
Out[59]: ('download_timeout', 175)

In [60]: request.meta.popitem()
Out[60]: ('handle_httpstatus_all', True)
А следующая обновляет значения -
In []:
In [63]: request.meta.update()

In [64]: request.meta
Out[64]:
{'depth': 0,
 'download_latency': 0.5850000381469727,
 'download_slot': 'www.dmoz.org'}

scrapy.http.Request.copy -Пытаемся найти описание атрибутов, но находим еще одну возможность

copy() Return a new Request which is a copy of this Request. See also: Passing additional data to callback functions.
replace([url, method, headers, body, cookies, meta, encoding, dont_filter, callback, errback]) Return a Request object with the same members, except for those members given new values by whichever keyword arguments are specified. The attribute Request.meta is copied by default (unless a new value is given in the meta argument). See also Passing additional data to callback functions.
Насколько я понял, этой командой можно менять любые параметры запроса. По крайней мере, на первый взгляд кажется, что перечислены все. Далее попробуем, но сначала проверим, что значит "shallow"
Assignment statements in Python do not copy objects, they create bindings between a target and an object. For collections that are mutable or contain mutable items, a copy is sometimes needed so one can change one copy without changing the other. This module provides generic shallow and deep copy operations (explained below).

Как перевести "generic shallow"

Что это за связки создаются? Да еще мелководные? Если перевести "shallow", как "поверхностный"..., то напрашивается механизм наследования. Есть некий первоначальный слой параметров, мы на него накладываем "прозрачный" слой-потомок с вкраплениями новых значений параметров. Хорошо знакомая философия заплаток (патчей).
Получается, что я могу прсто заменить (или добавить) в существующем запросе некоторые параметры по моему выбору, а потом исспользовать этот новый запрос в команде fetch:
In []:
In [91]: response.url
Out[91]: 'http://www.dmoz.org/Computers/Programming/Languages/Python/Books/'

In [92]: re1=request.replace(url='http://www.dmoz.org',headers=header_vals)

In [93]: re1.url
Out[93]: 'http://www.dmoz.org'

In [94]: re2=fetch(re1)
2014-08-14 15:33:31+0400 [default] DEBUG: Crawled (200) <GET http://www.dmoz.org> (referer: None)
[s] Available Scrapy objects:
[s]   _10        <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>
[s]   _18        <GET http://www.dmoz.org/Computers/Programming/>
[s]   _68        <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>
[s]   _70        <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>
[s]   _73        <GET http://www.dmoz.org>
[s]   _85        <CrawlerSettings module=None>
[s]   _88        <GET http://www.dmoz.org>
[s]   item       {}
[s]   re1        <GET http://www.dmoz.org>
[s]   request    <GET http://www.dmoz.org>
[s]   response   <200 http://www.dmoz.org>
[s]   rr         <GET http://www.dmoz.org/Computers/Programming/Languages/Python/Books/>
[s]   sel        <Selector xpath=None data=u'<html><head>\r\n<title>DMOZ - the Open Dir'>
[s]   settings   <CrawlerSettings module=None>
[s]   spider     <BaseSpider 'default' at 0x61bd358>
[s] Useful shortcuts:
[s]   shelp()           Shell help (print this help)
[s]   fetch(req_or_url) Fetch request (or URL) and update local objects
[s]   view(response)    View response in a browser

In [95]: re2.url
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-95-6cb5bec6e840> in <module>()
----> 1 re2.url

AttributeError: 'NoneType' object has no attribute 'url'

In [96]: re2.headers
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-96-88d2c589969c> in <module>()
----> 1 re2.headers

AttributeError: 'NoneType' object has no attribute 'headers'

In [97]: response.url
Out[97]: 'http://www.dmoz.org'
Интересно, re2 - не объект. Fetch - только команда... которая переприсваивает объект response. Здесь я не стал распечатывать response.body, но, поверьте на слово - тело ответа тоже обновилось.
In []:

In []:
In [101]: request.meta
Out[101]:
{'depth': 0,
 'download_latency': 2.319999933242798,
 'download_slot': 'www.dmoz.org',
 'download_timeout': 180}
In []:
new_meta= {'depth': 0,'download_latency': 2.319999933242798, 'download_slot': 'www.dmoz.org', 'download_timeout': 180}

И самый простой способ раоты в оболочке - это стартовать из папки проекта, а в проекте установить все, например PROXY

After spending forever trying to debug, it turns out that HttpProxyMiddleware actually expects http_proxy environment variable to be set. The middleware will not be loaded if http_proxy is not set. Therefore, I set http_proxy and bob's your uncle! Everything works!
In []:
class scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware

This middleware sets the HTTP proxy to use for requests, by setting the proxy meta value to Request objects.

Like the Python standard library modules urllib and urllib2, it obeys the following environment variables:

http_proxy
https_proxy
no_proxy
Прочитал я еще Setting Scrapy proxy middleware to rotate on each request Это очень важный пост. Решил проверить код оттуда. Оказалось, что надо испоьзовать http_proxy вместо proxy... Но обо всем по-порядку: Сначала присваиваем rr=request, потом можно делаьть с rr что угодно. Например присвоить новый атрибут... Я сначала присвоил proxy... Потом пришлось его убирать, оказалось достаточным присвоить пустую строку. После этой ошибки я "забыл" про rr и оперировал непосредственно request... Оказалось, что это почти ссылки на один и тот же объект - generic shallow (см. выше) Я поменял слой родителя, а надо было бы "rr"
In []:
In [2]: request.meta['proxy'] = "127.0.0.1:8118"
In []:
In [5]: rr=request

In [9]: rr.url
Out[9]: 'http://dmoz.org'

In [10]: rr.meta
Out[10]:
{'download_latency': 0.5360000133514404,
 'download_slot': 'dmoz.org',
 'download_timeout': 180,
 'handle_httpstatus_all': True,
 'proxy': '127.0.0.1:8118',
 'redirect_ttl': 20}

In [11]: request.meta['http_proxy'] = "127.0.0.1:8118"

In [12]: request.meta['proxy'] = ""

In [13]: rr.meta
Out[13]:
{'download_latency': 0.5360000133514404,
 'download_slot': 'dmoz.org',
 'download_timeout': 180,
 'handle_httpstatus_all': True,
 'http_proxy': '127.0.0.1:8118',
 'proxy': '',
 'redirect_ttl': 20}
Отметим особенность "generic shallow" Сначала я задал параметр proxy для request, а потом пррисвоил rr=request... А надо было наоборот ! Получилось, что потомок rr тут жу унаследовал изменения и успешно выполнил:
In []:
In [14]: fetch(rr)
2014-08-14 21:07:07+0400 [default] DEBUG: Redirecting (301) to <GET http://www.dmoz.org/> from
2014-08-14 21:07:08+0400 [default] DEBUG: Crawled (200) <GET http://www.dmoz.org/> (referer: N
[s] Available Scrapy objects:
[s]   item       {}
[s]   request    <GET http://dmoz.org>
[s]   response   <200 http://www.dmoz.org/>
[s]   rr         <GET http://dmoz.org>
[s]   sel        <Selector xpath=None data=u'<html><head>\r\n<title>DMOZ - the Open Dir'>
[s]   settings   <CrawlerSettings module=None>
[s]   spider     <BaseSpider 'default' at 0x4c28978>
[s] Useful shortcuts:
[s]   shelp()           Shell help (print this help)
[s]   fetch(req_or_url) Fetch request (or URL) and update local objects
[s]   view(response)    View response in a browser

In [15]: request.
request.body        request.copy        request.errback     request.method      request.url
request.callback    request.dont_filter request.headers     request.priority
request.cookies     request.encoding    request.meta        request.replace

In [15]: request.meta
Out[15]:
{'download_latency': 0.5060000419616699,
 'download_slot': 'dmoz.org',
 'download_timeout': 180,
 'handle_httpstatus_all': True,
 'http_proxy': '127.0.0.1:8118',
 'proxy': '',
 'redirect_ttl': 20}
Итак, я кажется загрузился через TOR. Конечно же, я сначала запустил Privoxy, а потом TOR browser. Напомню, что конфиг Privoxy отредактирован так, чтобы была цепочка прокси 8118 -> 9051 (пост о настройке есть в этом блоге)

Попробуем теперь заменить url

In []:
In [23]: rr.replace(url="https://check.torproject.org/?lang=en_US")
Out[23]: <GET https://check.torproject.org/?lang=en_US>

In [24]: fetch(rr)
2014-08-14 21:40:06+0400 [default] DEBUG: Redirecting (301) to <GET http://www.dmoz.org/> from <GET http://dmoz.org>
2014-08-14 21:40:08+0400 [default] DEBUG: Crawled (200) <GET http://www.dmoz.org/> (referer: None)
[s] Available Scrapy objects:
[s]   _          <GET https://check.torproject.org/?lang=en_US>
[s]   _23        <GET https://check.torproject.org/?lang=en_US>
[s]   item       {}
[s]   request    <GET http://dmoz.org>
[s]   response   <200 http://www.dmoz.org/>
[s]   rr         <GET http://dmoz.org>
[s]   sel        <Selector xpath=None data=u'<html><head>\r\n<title>DMOZ - the Open Dir'>
[s]   settings   <CrawlerSettings module=None>
[s]   spider     <BaseSpider 'default' at 0x4c28978>
[s] Useful shortcuts:
[s]   shelp()           Shell help (print this help)
[s]   fetch(req_or_url) Fetch request (or URL) and update local objects
[s]   view(response)    View response in a browser

In [25]: rr.url
Out[25]: 'http://dmoz.org'

In [26]: request.replace(url="https://check.torproject.org/?lang=en_US")
Out[26]: <GET https://check.torproject.org/?lang=en_US>

In [27]: request.url
Out[27]: 'http://dmoz.org'

In [28]: response.url
Out[28]: 'http://www.dmoz.org/'
Не понятно, замена произошла? Да! А парсится все равно dmoz... изловчимся и зададим парсинг прямо из ячейки
In []:
In [30]: _23
Out[30]: <GET https://check.torproject.org/?lang=en_US>

In [31]: fetch(_23)
2014-08-14 21:46:30+0400 [default] DEBUG: Crawled (200) <GET https://check.torproject.org/?lang=en_US> (referer: None)
[s] Available Scrapy objects:
[s]   _          <GET https://check.torproject.org/?lang=en_US>
[s]   _23        <GET https://check.torproject.org/?lang=en_US>
[s]   _26        <GET https://check.torproject.org/?lang=en_US>
[s]   _30        <GET https://check.torproject.org/?lang=en_US>
[s]   item       {}
[s]   request    <GET https://check.torproject.org/?lang=en_US>
[s]   response   <200 https://check.torproject.org/?lang=en_US>
[s]   rr         <GET http://dmoz.org>
[s]   sel        <Selector xpath=None data=u'<html lang="en_US">\n<head>\n  <meta chars'>
[s]   settings   <CrawlerSettings module=None>
[s]   spider     <BaseSpider 'default' at 0x4c28978>
[s] Useful shortcuts:
[s]   shelp()           Shell help (print this help)
[s]   fetch(req_or_url) Fetch request (or URL) and update local objects
[s]   view(response)    View response in a browser

In [32]: response.url
Out[32]: 'https://check.torproject.org/?lang=en_US'

In [33]: view(response)
Out[33]: True
Смотрю в браузере, что получил... А там "Sorry. You are not using Tor" Почему не сработал прокси?
In []:
In [38]: request.meta
Out[38]:
{'depth': 0,
 'download_latency': 1.9709999561309814,
 'download_slot': 'dmoz.org',
 'download_timeout': 180,
 'handle_httpstatus_all': True,
 'http_proxy': '127.0.0.1:8118',
 'proxy': '',
 'redirect_ttl': 20}

In [39]: response.meta
Out[39]:
{'depth': 0,
 'download_latency': 1.9709999561309814,
 'download_slot': 'dmoz.org',
 'download_timeout': 180,
 'handle_httpstatus_all': True,
 'http_proxy': '127.0.0.1:8118',
 'proxy': '',
 'redirect_ttl': 20}

In [40]: response.url
Out[40]: 'https://check.torproject.org/?lang=en_US'

In [41]:


Посты чуть ниже также могут вас заинтересовать

2 комментария:

  1. А вот вариант для простейшего варианта middleware:

    class ProxyMiddleware(object):
    def process_request(self, request, spider):
    request.meta['proxy'] = settings.get('HTTP_PROXY')

    ОтветитьУдалить
    Ответы
    1. Смотрю на эти три строчки кода с возмущением. Блоггер, зараза, убрал все "инденты" из комментария... и это меня раздражает. Тешу себя надеждой, что это признак моего нарастающего профессионализма...

      Удалить