Объект shell request - обертка для реклизации класса Request. Пример работы с объектом из оболочки здесь Problem logging into Facebook with Scrapy, а в этом посте мои эксперименты с request response, settings... Пост кончается тем, что я соединился с проверочным сервисом TOR (через Privoxy - TOR), а он мне сказал SORRY...
Problem logging into Facebook with Scrapy
Requests and Responses
8.17. copy — Shallow and deep copy operations
HttpProxyMiddleware
Setting Scrapy proxy middleware to rotate on each request
Requests and Responses
8.17. copy — Shallow and deep copy operations
HttpProxyMiddleware
Setting Scrapy proxy middleware to rotate on each request
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
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
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.
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]:
Посты чуть ниже также могут вас заинтересовать
А вот вариант для простейшего варианта middleware:
ОтветитьУдалитьclass ProxyMiddleware(object):
def process_request(self, request, spider):
request.meta['proxy'] = settings.get('HTTP_PROXY')
Смотрю на эти три строчки кода с возмущением. Блоггер, зараза, убрал все "инденты" из комментария... и это меня раздражает. Тешу себя надеждой, что это признак моего нарастающего профессионализма...
Удалить