Closed makorne closed 7 years ago
Возможно сменилась верстка и парсер не может "вытащить" итемы, но при этом работает пагинация. На днях попробую глянуть.
Может как-то выводить сообщение об этом? Наверно такие ситуации не редкость, мало ли где верстка изменится... Если б сразу знать где копать - можно исправить.
Починил парсер - дело было в xpath. Ввел команду проверки xpath на актуальность
$ docker-compose run --rm crawler manage check-xpath "https://sfbay.craigslist.org/search/bia?is_paid=all&search_distance_type=mi&query=kid+bike"
Осторожно, craigslist начал банить по IP
Может как-то выводить сообщение об этом?
Сходу не могу представить где и как оповещать. Но так как есть команда проверки xpath на актуальность, то можно на её основе:
Наверняка обе вещи нужны, чтоб бесполезные баны не плодить и время зря не тратить.
Согласен, что такое для реального проекта полезно, но в рамках данного проекта смысла не вижу.
Если есть желание, то попробуйте реализовать это сами, помогу сделать ревью.
Мне б понять как тут вообще тест запускать... :) Какой строкой юнит-тест запустить?
Ay, бог-созидатель шайтан-помпы! Ну подскажи плиз - какой строкой mock тест проекта запустить?
Вот этой командой
$ docker-compose run --rm crawler manage check-xpath "https://sfbay.craigslist.org/search/bia?is_paid=all&search_distance_type=mi&query=kid+bike"
Она скачает html и подсунет парсеру, в ответ будет:
xpath: OUT OF DATE
значит парсер имеет не актуальные xpathxpath: ACTUAL
значит парсер имеет актуальный xpath и его можно запускатьА вот mock тест проекта
нету. Есть только проверка актуальности xpath и все.
Остальные части проекта не проверяются.
А для чего тогда файлы в папочке tests? Ты же их делал, начит и запускал как-то..
И точно есть же тесты, забыл про них совсем.
Запустить их можно вот так.
$ docker-compose run --rm crawler py.test
Starting pompcraigslistexample_grafana_1
Starting pompcraigslistexample_zookeeper_1
Starting pompcraigslistexample_redis_1
Starting pompcraigslistexample_kafka_1
=========================================================== test session starts ============================================================
platform linux -- Python 3.6.1, pytest-3.0.7, py-1.4.33, pluggy-0.4.0
rootdir: /root, inifile:
collected 6 items
tests/test_crawler.py ..
tests/test_downloader.py .
tests/test_pipeline.py ..
tests/test_queue.py .
======================================================== 6 passed in 25.35 seconds =========================================================
Ни как не пойму что делать... И гугл не знает. Может как-то не так для utf8 парсится в движке? Куда это "obj, end = self.scan_once(s, idx) " вставить?
item.photos = [
i['url'] for i in json.loads(
self.ITEM_PHOTOS_RE.search(response.body).groups()[0]
)
]
`_____________________________ test_crawler_item_parse ______________________________
self = <json.decoder.JSONDecoder object at 0x7fbd8dad3ba8>
s = 'https://www.testing.com/v1/AUTH_svc.ui/PIX33/319c2613-d5bf-449b-98fb-744.JPG'
idx = 0
def raw_decode(self, s, idx=0):
"""Decode a JSON document from ``s`` (a ``str`` beginning with
a JSON document) and return a 2-tuple of the Python
representation and the index in ``s`` where the document ended.
This can be used to decode a JSON document from a string that may
have extraneous data at the end.
"""
try:
> obj, end = self.scan_once(s, idx)
E StopIteration: 0
/usr/local/lib/python3.6/json/decoder.py:355: StopIteration`
Понял что какая-то проблема получением список фоток - ошибка декодирования json.
Вот в исходной HMTL странице есть кусок JS кода
var imgList = [{"shortid":"kN64FieYYgT","url":"http://images.craigslist.org/01717_kN64FieYYgT_600x450.jpg","thumb":"http://images.craigslist.org/01717_kN64FieYYgT_50x50c.jpg","imgid":"0:01717_kN64FieYYgT"}];
Наш парсер делает следующее
>>> import re
>>> import json
>>> source = """
... var imgList = [{"shortid":"kN64FieYYgT","url":"http://images.craigslist.org/01717_kN64FieYYgT_600x450.jpg","thumb":"http://images.craigslist.org/01717_kN64FieYYgT_50x50c.jpg","imgid":"0:01717_kN64FieYYgT"}];
... """
>>> ITEM_PHOTOS_RE = re.compile('var imgList\ =\ (.*)\;')
>>> images = ITEM_PHOTOS_RE.search(source).groups()[0]
>>> images
'[{"shortid":"kN64FieYYgT","url":"http://images.craigslist.org/01717_kN64FieYYgT_600x450.jpg","thumb":"http://images.craigslist.org/01717_kN64FieYYgT_50x50c.jpg","imgid":"0:01717_kN64FieYYgT"}]'
>>> [i['url'] for i in json.loads(images)]
['http://images.craigslist.org/01717_kN64FieYYgT_600x450.jpg']
Проверти что передается в self.ITEM_PHOTOS_RE.search(response.body).groups()[0]
в параметре response.body
есть ли там нужный imgList
?
Не знаю как правильно это сделать.
Если так:
log.debug( self.ITEM_PHOTOS_RE.search(response.body).groups()[0])
Ругается матом
===================================== FAILURES _ test_crawler_item_parse __
def test_crawler_item_parse():
request = ItemRequest(
session_id='somesession',
url='mocked',
city_code='',
)
items = crawler.extract_items(
mock_response(
'data/item.html',
request_instance=request,
),
)
for item in items:
tests/test_crawler.py:56:
craigslist/crawler.py:90: in extract_items yield from self._parse_item(response, tree) craigslist/crawler.py:167: in _parse_item log.debug( self.ITEM_PHOTOS_RE.search(response.body).groups()[0])
s = None, encoding = None, cls = None, object_hook = None, parse_float = None parse_int = None, parse_constant = None, object_pairs_hook = None, kw = {}
def loads(s, *, encoding=None, cls=None, object_hook=None, parse_float=None,
parse_int=None, parse_constant=None, object_pairs_hook=None, **kw):
"""Deserialize ``s`` (a ``str``, ``bytes`` or ``bytearray`` instance
containing a JSON document) to a Python object.
``object_hook`` is an optional function that will be called with the
result of any object literal decode (a ``dict``). The return value of
``object_hook`` will be used instead of the ``dict``. This feature
can be used to implement custom decoders (e.g. JSON-RPC class hinting).
``object_pairs_hook`` is an optional function that will be called with the
result of any object literal decoded with an ordered list of pairs. The
return value of ``object_pairs_hook`` will be used instead of the ``dict``.
This feature can be used to implement custom decoders that rely on the
order that the key and value pairs are decoded (for example,
collections.OrderedDict will remember the order of insertion). If
``object_hook`` is also defined, the ``object_pairs_hook`` takes priority.
``parse_float``, if specified, will be called with the string
of every JSON float to be decoded. By default this is equivalent to
float(num_str). This can be used to use another datatype or parser
for JSON floats (e.g. decimal.Decimal).
``parse_int``, if specified, will be called with the string
of every JSON int to be decoded. By default this is equivalent to
int(num_str). This can be used to use another datatype or parser
for JSON integers (e.g. float).
``parse_constant``, if specified, will be called with one of the
following strings: -Infinity, Infinity, NaN.
This can be used to raise an exception if invalid JSON numbers
are encountered.
To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
kwarg; otherwise ``JSONDecoder`` is used.
The ``encoding`` argument is ignored and deprecated.
"""
if isinstance(s, str):
if s.startswith('\ufeff'):
raise JSONDecodeError("Unexpected UTF-8 BOM (decode using utf-8-sig)",
s, 0)
else:
if not isinstance(s, (bytes, bytearray)):
raise TypeError('the JSON object must be str, bytes or bytearray, '
'not {!r}'.format(s.__class__.__name__))
E TypeError: the JSON object must be str, bytes or bytearray, not 'NoneType'
/usr/local/lib/python3.6/json/init.py:348: TypeError ------------------------------- Captured stderr call ------------------------------- DEBUG:craigslist.crawler:https://www.testing.com/v1/AUTH_svc.ui/PIX33/319c2613-d5bf-449b-98fb-744.JPG `
Я парщу другую страницу, там регексп такой: ITEM_PHOTOS_RE = re.compile('(https:\/\/.+.JPG)') В онлайн тестере работает, тут тоже, но видимо изза ошибки UTF8 на странице его зацикливает, надо куда-то "obj, end = self.scan_once(s, idx) " вставить.
re.compile
в исходном парсинге craigslist нужен для выдерания JSON строк из JS кода на странице - костыль по сути.
У Вас же похоже "фотки" есть полноценные DOM элементы их можно и лучше "выдерать" через XPATH.
pythex.org выгрызает 10 на этой странице, ну если так проще... Спасибо!
А как лучше это сделать?
photos = item.xpath('<some-xpath-to-photo-links>/img/@src')
Вот пример XPATH, который должен вытащить все ссылки на аватарки участников обсуждения с это страницы github - //div[contains(@class, 'timeline-comment-avatar')]//img/@src
см скрин
Спасибище, я просто не знаю как на питоне лучше .
вот это интересовало: photos = item.xpath('
Чета туплю... item.photos = response.body.xpath('/img/@src')
File "/root/craigslist/crawler.py", line 163 E item.photos = response.body.xpath('/img/@src') E ^ E IndentationError: unindent does not match any outer indentation level _______________________ ERROR collecting tests/test_queue.py _______________________ lib/python3.6/site-packages/pytest-3.0.7-py3.6.egg/_pytest/python.py:418: in _importtestmodule
<div class="viewAllPhotosRelative">
<img src="photos_files/319c2613-d8bf-44b-98fb-748294.JPG"
item.photos = response.body.xpath('//div[contains(@class, "viewAllPhotosRelative")]/img/@src')
File "/root/craigslist/crawler.py", line 163
E item.photos = response.body.xpath('//div[contains(@class, "viewAllPhotosRelative")]//img/@src')
E ^
E IndentationError: unindent does not match any outer indentation level
Стрелочка постоянно показывает на последнюю скобку
Class - с маленькой, тут чтото тупит автозаменяет
Вам нужно редактор настроить так что бы вместо tab
были 4 space
.
И во всех файлах tab
нужно заменить на 4 space
и тогда все должно завестись
Тогда:
item.photos = response.body.xpath('//div[contains(@class, "viewAllPhotosRelative")]//img/@src')
E AttributeError: 'str' object has no attribute 'xpath'
Попробуйте так
html.fromstring(response.body).xpath('//div[contains(@class, "viewAllPhotosRelative")]//img/@src')
Спасибо, я во как нашел tree.xpath(...
в кафке query отображаются(current 2, total 60), но все остальное по нулям. в django спарщенного тоже нет.
В логе:
2017-03-29 22:37:01,057 DEBUG MainProcess craigslist.queue Put to queue: <ListRequest session_id:mountainbike url:https://chicago.craigslist.org/se$ 2017-03-29 22:37:01,059 DEBUG MainProcess craigslist.queue From queue: <ListRequest session_id:mountainbike url:https://newyork.craigslist.org/sear$ 2017-03-29 22:37:01,059 DEBUG MainProcess craigslist.downloader [AiohttpDownloader] Start fetch: https://newyork.craigslist.org/search/bia?s=360&is$ 2017-03-29 22:37:02,473 DEBUG MainProcess craigslist.downloader [AiohttpDownloader] Done https://newyork.craigslist.org/search/bia?s=360&is_paid=al$ 2017-03-29 22:37:02,476 DEBUG Process-1 pomp.contrib.concurrent Crawler worker pid=12 params={'worker_class': <class 'craigslist.crawler.CraigsList$ 2017-03-29 22:37:02,497 DEBUG MainProcess craigslist.queue From queue: <ListRequest session_id:mountainbike url:https://sfbay.craigslist.org/search$ 2017-03-29 22:37:02,497 DEBUG MainProcess craigslist.downloader [AiohttpDownloader] Start fetch: https://sfbay.craigslist.org/search/bia?s=360&is_p$ 2017-03-29 22:37:04,062 DEBUG MainProcess craigslist.downloader [AiohttpDownloader] Done https://sfbay.craigslist.org/search/bia?s=360&is_paid=all&$ 2017-03-29 22:37:04,065 DEBUG Process-2 pomp.contrib.concurrent Crawler worker pid=13 params={'worker_class': <class 'craigslist.crawler.CraigsList$ 2017-03-29 22:37:04,085 DEBUG MainProcess craigslist.queue From queue: <ListRequest session_id:mountainbike url:https://chicago.craigslist.org/sear$ 2017-03-29 22:37:04,086 DEBUG MainProcess craigslist.downloader [AiohttpDownloader] Start fetch: https://chicago.craigslist.org/search/bia?s=360&is$ 2017-03-29 22:37:05,516 DEBUG MainProcess craigslist.downloader [AiohttpDownloader] Done https://chicago.craigslist.org/search/bia?s=360&is_paid=al$ 2017-03-29 22:37:05,519 DEBUG Process-1 pomp.contrib.concurrent Crawler worker pid=12 params={'worker_class': <class 'craigslist.crawler.CraigsList$ 2017-03-29 22:48:56,910 DEBUG MainProcess craigslist.queue Put to queue: <ListRequest session_id:mountainbike url:https://newyork.craigslist.org/se$ 2017-03-29 22:48:56,911 DEBUG MainProcess craigslist.queue Put to queue: <ListRequest session_id:mountainbike url:https://sfbay.craigslist.org/sear$ 2017-03-29 22:48:56,912 DEBUG MainProcess craigslist.queue Put to queue: <ListRequest session_id:mountainbike url:https://chicago.craigslist.org/se$ 2017-03-29 22:51:37,266 DEBUG MainProcess craigslist.queue Put to queue: <ListRequest session_id:mountainbike url:https://newyork.craigslist.org/se$ 2017-03-29 22:51:37,267 DEBUG MainProcess craigslist.queue Put to queue: <ListRequest session_id:mountainbike url:https://sfbay.craigslist.org/sear$ 2017-03-29 22:51:37,268 DEBUG MainProcess craigslist.queue Put to queue: <ListRequest session_id:mountainbike url:https://chicago.craigslist.org/se$