Захотелось тут автоматизировать одну задачу. Вот у нас есть название какой-нибудь штуки, скажем, TP-LINK TL-SF1005D. А нам нужно: несколько изображений, технические характеристики и самую низкую цену в регионе. И, конечно, не руками всё это искать и сохранять. А ещё хорошо бы из одного источника – по одному шаблону.
Источников сегодня полно, так что второй вопрос сразу решаем – берём одну из самых известных маркет-площадок и, по совместительству, базу данных товаров. Ну, сами понимаете, какую.
Хорошо, а как не делать ничего руками? Самое адекватное решение это, конечно, API.
Только вот цены за API у нашего "маркета" как раз неадекватные. И только для юр. лиц.
При этом информация доступна – бери не хочу. Даже особого программирования не нужно.
Веселья ради и опыта для сделаем парсинг на чистом cmd. Ну, почти.
Поскольку у нас есть только название, прежде всего нам нужна ссылка на страницу товара. Смотрим сайт и видим, что получить её можно, отправив поисковый запрос, типа такого:
Сразу натыкаемся на несколько проблем.
Во-первых, suggest (угадывание) срабатывает из браузера, но не из wget по ряду причин. Во-вторых, мы не знаем синтаксиса запроса, а перебирать варианты долго. В третьих, после второго и далее wget-запроса с некоторой вероятностью нас блокируют и просят ввести капчу.
Это уже много работы, а мы ленивые.
Значит, надо подумать outside the box.
Раз нужно делать как можно меньше запросов к нашему market'у, мы можем запросить другой, не такой капризный сервер для получения прямой ссылки. Поисковик какой-нибудь. Но это опять парсинг и уже по другом шаблону. А попроще нельзя?
Можно! Ищем, есть ли у нашего маркета расширение для поиска из адресной строки браузера. Серверы-обработчики такого поиска, скорее всего, настроены на постоянную обработку автоматизированных запросов. А значит, капчи там нет, угадывание работает всегда, а сам ответ сервера гораздо меньше и проще.
Так и поступим:
В ответ получим что-то вроде:
Крутотенюшка! И ссылка, и изображение, и минимальная цена, и в машиночитаемом формате. Какой смысл был API закрывать?
Итак, мы нежданно получили почти всё, что нам было нужно.
Ссылку получим через grep (родные виндовские find и findstr оказались не слишком хороши), как-то так:
И скачаем нужную нам страницу с данными:
Вроде как уже всё. Но "есть нюансы":
1. У нас полноценная современная html-страница. А значит, около мегабайта текста с кошмарным форматированием. К тому же, grep из gnuwin32 в мультистроковом режиме не работает;
2. У нас по-прежнему капча на повторных запросах;
3. Мы кириллические юзеры. В странице UTF, в cmd CP866. Переключиться на 65001 можно, но передача параметров на кириллице даже в grep не срабатывает.
Чтобы было меньше капчи, добавим в wget некоторый закос под браузер:
Начнём с простого. Вынем из нашей страницы минимальную цену:
Можно сразу сделать её ещё ниже, во имя победы в конкурентной борьбе:
А теперь фото. Их там много, в том числе и не наши, а нам нужно 2-3 штуки. Поэтому берём первые штук шесть:
и сразу их качаем. Сервер картинок тоже отдельный и не капчит. Но задержку между запросами лучше всё же оставить:
Для красоты и SEO можем переименовать наши фото. Видов спереди, "панорамно" и сбоку будет вполне достаточно.
Первая картинка обычно всегда основная:
Для получения данных об изображениях в командной строке используем бесплатную MediaInfo CLI.
Остальные фото можно обработать по такой логике: следующая после основной картинка будет "панорамной", если она такого же или близкого размера к главному фото. Первая узкая (до 300 пикселей) картинка это, скорее всего, вид сбоку:
А теперь самое интересное. Вынимаем кириллические характеристики в UTF из кошмарно форматированного html с примесью скриптов, BASE64 и кучи лишней информации.
К счастью, компьютеры не оперируют нечёткой логикой, и как бы страшно не выглядела оцифрованная информация, у неё есть чёткая структура. Вынуть характеристики из нашего market'а нам помогут регулярные выражения. Да-да, те самые ещё более неудобочитаемые и с виду совершенно бессмысленные символы. На самом деле, всё становится гораздо проще, когда используешь онлайн-чекеры (мне лично нравится regex101.com). Вот наша регулярка:
Да, я сам её боюсь. Но если по кускам, то
((?<=<span class=\"n-product-spec__name-inner\">) - нужная нам строка начинается с определённого тега, при этом сам тег исключаем
(([a-zA-Z0-9А-Яа-я-:\"\.,;\/()]|\s){1,64}) - нужная нам строка содержит символы: a-zA-Z0-9А-Яа-я-:\"\.,;\/(), то есть алфавиты русский и английский, цифры и служебные символы -:".,;/(), при этом "\" - знак экранирования, ИЛИ (знак "|") пробельные символы (обозначаются "\s") в количестве {1,64}
(?=<)) - нужная строка заканчивается символом "<", но в строку его не включаем
И ничего страшного :)
Правда, gnuwin32 grep UTF-параметры на вход никак не осиливает и в мультистроковом режиме тоже не работает. Поэтому пришлось набросать быстренько прогу на C# исходник на Github сама программа (бесплатно и без смс, как всегда).
Делаем просто:
Можно сразу отформатировать в html список для красивости:
Вот мы и получили всё, что хотели.
Как видим, не сильно сложно. Самое смешное во всём этом даже не cmd, а то, что если купить какой-нибудь готовый парсер, то делать придётся практически то же самое. Всё равно ковырять страницу, выискивать теги, запрашивать ссылки. А если у нашего маркета что-то поменяется, то нет гарантии, что платный парсер с закрытым кодом не перестанет работать. В нашем же случае изменения внести проще простого, даже компилятор не нужен.
Защита от парсинга на market'ах, в общем, номинальная. Открыли бы API с ограниченными запросами, проблем было бы меньше для них же.
Хочется загрузить полученную информацию куда-нибудь в базу данных или на сайт? Да не вопрос. Переведём в xml, csv, sql и вообще что угодно. Но об этом – в следующий раз.
Да пребудет с вами Regex, да обойдёт вас капча!
Источников сегодня полно, так что второй вопрос сразу решаем – берём одну из самых известных маркет-площадок и, по совместительству, базу данных товаров. Ну, сами понимаете, какую.
Хорошо, а как не делать ничего руками? Самое адекватное решение это, конечно, API.
Только вот цены за API у нашего "маркета" как раз неадекватные. И только для юр. лиц.
При этом информация доступна – бери не хочу. Даже особого программирования не нужно.
Веселья ради и опыта для сделаем парсинг на чистом cmd. Ну, почти.
Поскольку у нас есть только название, прежде всего нам нужна ссылка на страницу товара. Смотрим сайт и видим, что получить её можно, отправив поисковый запрос, типа такого:
wget "http://market.some/search?redirect=2&suggest=test123"
Сразу натыкаемся на несколько проблем.
Во-первых, suggest (угадывание) срабатывает из браузера, но не из wget по ряду причин. Во-вторых, мы не знаем синтаксиса запроса, а перебирать варианты долго. В третьих, после второго и далее wget-запроса с некоторой вероятностью нас блокируют и просят ввести капчу.
Это уже много работы, а мы ленивые.
Значит, надо подумать outside the box.
Раз нужно делать как можно меньше запросов к нашему market'у, мы можем запросить другой, не такой капризный сервер для получения прямой ссылки. Поисковик какой-нибудь. Но это опять парсинг и уже по другом шаблону. А попроще нельзя?
Можно! Ищем, есть ли у нашего маркета расширение для поиска из адресной строки браузера. Серверы-обработчики такого поиска, скорее всего, настроены на постоянную обработку автоматизированных запросов. А значит, капчи там нет, угадывание работает всегда, а сам ответ сервера гораздо меньше и проще.
Так и поступим:
wget "http://server.some/suggest-rich?&srv=market&part=TP-LINK+TL-SF1005D"
В ответ получим что-то вроде:
["tp-link tl-sf1005d"],
["[{"img": "images.server.some/img_id30773513443.jpg", "text": "TP-LINK TL-SF1005D", "link": "market.some/product/1581115?hid", "prices": [{"max": 100500, "min": 1050}, "type": "model"}]]]
["[{"img": "images.server.some/img_id30773513443.jpg", "text": "TP-LINK TL-SF1005D", "link": "market.some/product/1581115?hid", "prices": [{"max": 100500, "min": 1050}, "type": "model"}]]]
Крутотенюшка! И ссылка, и изображение, и минимальная цена, и в машиночитаемом формате. Какой смысл был API закрывать?
Итак, мы нежданно получили почти всё, что нам было нужно.
Ссылку получим через grep (родные виндовские find и findstr оказались не слишком хороши), как-то так:
FOR /F "tokens=*" %%N IN ('grep -Po "(?<=product\\\/)(\S{1,16})(?=\?hid)" temp') DO SET LINK=%%N
И скачаем нужную нам страницу с данными:
wget "%LINK%/specifications" -O modelsheet
Вроде как уже всё. Но "есть нюансы":
1. У нас полноценная современная html-страница. А значит, около мегабайта текста с кошмарным форматированием. К тому же, grep из gnuwin32 в мультистроковом режиме не работает;
2. У нас по-прежнему капча на повторных запросах;
3. Мы кириллические юзеры. В странице UTF, в cmd CP866. Переключиться на 65001 можно, но передача параметров на кириллице даже в grep не срабатывает.
Чтобы было меньше капчи, добавим в wget некоторый закос под браузер:
wget --user-agent "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko" --no-http-keep-alive --load-cookies "ya_cook.txt" "%LINK%/specifications" -O modelsheet
Начнём с простого. Вынем из нашей страницы минимальную цену:
FOR /F "tokens=*" %%N IN ('grep -Po "\d{1,16}(?=(\s|\S){1,16}lowPrice)" modelsheet') DO SET /A PRICE=%%N
Можно сразу сделать её ещё ниже, во имя победы в конкурентной борьбе:
SET /A PRICE=%PRICE%-((%RANDOM%%%10)*10+100)
А теперь фото. Их там много, в том числе и не наши, а нам нужно 2-3 штуки. Поэтому берём первые штук шесть:
FOR /F "tokens=*" %%F in ('grep -Po "images(\S{1,70})original.jpg" modelsheet') DO
и сразу их качаем. Сервер картинок тоже отдельный и не капчит. Но задержку между запросами лучше всё же оставить:
(
wget "%%F" -O "!PHCOUNT!.jpg"
SET /A PHCOUNT=!PHCOUNT!+1
IF !PHCOUNT! GTR 6 GOTO ENDPHDWNLD
timeout 1
)
wget "%%F" -O "!PHCOUNT!.jpg"
SET /A PHCOUNT=!PHCOUNT!+1
IF !PHCOUNT! GTR 6 GOTO ENDPHDWNLD
timeout 1
)
Для красоты и SEO можем переименовать наши фото. Видов спереди, "панорамно" и сбоку будет вполне достаточно.
Первая картинка обычно всегда основная:
ren 1.jpg %MANUFACTURER%_%MODEL%_main.jpg
Для получения данных об изображениях в командной строке используем бесплатную MediaInfo CLI.
Остальные фото можно обработать по такой логике: следующая после основной картинка будет "панорамной", если она такого же или близкого размера к главному фото. Первая узкая (до 300 пикселей) картинка это, скорее всего, вид сбоку:
FOR /F "tokens=* skip=1" %%F IN ('dir /b *.jpg') DO (
FOR /F "tokens=*" %%S IN ('MediaInfo.x64-sse2.exe --INFORM^=Image^;%%Width%% %%F') DO (
SET TMPSIZE=%%S
)
IF !TMPSIZE! LSS 299 REN %%F !MANUFACTURER!_!MODEL!__side.jpg
)
REM Find wide photo by width, side photo excluded, renamed photos excluded
FOR /F "tokens=* skip=1" %%Y IN ('dir /b ?.jpg') DO (
FOR /F "tokens=*" %%S IN ('MediaInfo.x64-sse2.exe --INFORM^=Image^;%%Width%% %%Y') DO (
SET TMPSIZE=%%S
)
IF !TMPSIZE! LEQ !FRONTSIZE! REN %%Y !MANUFACTURER!_!MODEL!_wide.jpg && GOTO ENDPHRN
)
:ENDPHRN
FOR /F "tokens=*" %%S IN ('MediaInfo.x64-sse2.exe --INFORM^=Image^;%%Width%% %%F') DO (
SET TMPSIZE=%%S
)
IF !TMPSIZE! LSS 299 REN %%F !MANUFACTURER!_!MODEL!__side.jpg
)
REM Find wide photo by width, side photo excluded, renamed photos excluded
FOR /F "tokens=* skip=1" %%Y IN ('dir /b ?.jpg') DO (
FOR /F "tokens=*" %%S IN ('MediaInfo.x64-sse2.exe --INFORM^=Image^;%%Width%% %%Y') DO (
SET TMPSIZE=%%S
)
IF !TMPSIZE! LEQ !FRONTSIZE! REN %%Y !MANUFACTURER!_!MODEL!_wide.jpg && GOTO ENDPHRN
)
:ENDPHRN
А теперь самое интересное. Вынимаем кириллические характеристики в UTF из кошмарно форматированного html с примесью скриптов, BASE64 и кучи лишней информации.
К счастью, компьютеры не оперируют нечёткой логикой, и как бы страшно не выглядела оцифрованная информация, у неё есть чёткая структура. Вынуть характеристики из нашего market'а нам помогут регулярные выражения. Да-да, те самые ещё более неудобочитаемые и с виду совершенно бессмысленные символы. На самом деле, всё становится гораздо проще, когда используешь онлайн-чекеры (мне лично нравится regex101.com). Вот наша регулярка:
((?<=<span class=\"n-product-spec__name-inner\">)(([a-zA-Z0-9А-Яа-я-:\"\.,;\/()]|\s){1,64})(?=<))|((?<=<span class=\"n-product-spec__value-inner\">)(([a-zA-Z0-9А-Яа-я-:\"\.,;\+()]|\s){1,80})(?=<))|((<h2 class="title title_size_18">)(([a-zA-Z0-9А-Яа-я-:\"\.,;()]|\s){1,64})(</h2>))
Да, я сам её боюсь. Но если по кускам, то
((?<=<span class=\"n-product-spec__name-inner\">) - нужная нам строка начинается с определённого тега, при этом сам тег исключаем
(([a-zA-Z0-9А-Яа-я-:\"\.,;\/()]|\s){1,64}) - нужная нам строка содержит символы: a-zA-Z0-9А-Яа-я-:\"\.,;\/(), то есть алфавиты русский и английский, цифры и служебные символы -:".,;/(), при этом "\" - знак экранирования, ИЛИ (знак "|") пробельные символы (обозначаются "\s") в количестве {1,64}
(?=<)) - нужная строка заканчивается символом "<", но в строку его не включаем
И ничего страшного :)
Правда, gnuwin32 grep UTF-параметры на вход никак не осиливает и в мультистроковом режиме тоже не работает. Поэтому пришлось набросать быстренько прогу на C# исходник на Github сама программа (бесплатно и без смс, как всегда).
Делаем просто:
cwr modelsheet regex.txt out.txt
Можно сразу отформатировать в html список для красивости:
FOR /F "tokens=*" %%S in (out.txt) DO (
ECHO "%%S" | FIND "h2"
SET /A STRPTR=!ERRORLEVEL!
CHCP 65001
IF !STRPTR!==0 IF !COUNT! NEQ 0 SET OUTSTR=!OUTSTR!^</dl^>
IF !STRPTR!==0 SET OUTSTR=!OUTSTR!%%S
IF !STRPTR!==0 SET OUTSTR=!OUTSTR!^<dl class="prspec"^> && SET /A COUNT=0
SET /A PAR=!COUNT!%%2
IF !STRPTR! NEQ 0 IF !PAR! NEQ 0 SET OUTSTR=!OUTSTR!^<dd class="prspec"^>
IF !STRPTR! NEQ 0 IF !PAR! NEQ 0 SET OUTSTR=!OUTSTR!%%S
IF !STRPTR! NEQ 0 IF !PAR! NEQ 0 SET OUTSTR=!OUTSTR!^</dd^>
IF !STRPTR! NEQ 0 IF !PAR! EQU 0 SET OUTSTR=!OUTSTR!^<dt class="prspec"^>
IF !STRPTR! NEQ 0 IF !PAR! EQU 0 SET OUTSTR=!OUTSTR!%%S
IF !STRPTR! NEQ 0 IF !PAR! EQU 0 SET OUTSTR=!OUTSTR!^</dt^>
IF !STRPTR! NEQ 0 SET /A COUNT=!COUNT!+1
SET /A STRCOUNT=!STRCOUNT!-1
IF !STRCOUNT! EQU 0 SET OUTSTR=!OUTSTR!^</dl^>^" && ECHO !OUTSTR!>>!OUTFLNM! && GOTO FORMEND
CHCP 866
)
:FORMEND
ECHO "%%S" | FIND "h2"
SET /A STRPTR=!ERRORLEVEL!
CHCP 65001
IF !STRPTR!==0 IF !COUNT! NEQ 0 SET OUTSTR=!OUTSTR!^</dl^>
IF !STRPTR!==0 SET OUTSTR=!OUTSTR!%%S
IF !STRPTR!==0 SET OUTSTR=!OUTSTR!^<dl class="prspec"^> && SET /A COUNT=0
SET /A PAR=!COUNT!%%2
IF !STRPTR! NEQ 0 IF !PAR! NEQ 0 SET OUTSTR=!OUTSTR!^<dd class="prspec"^>
IF !STRPTR! NEQ 0 IF !PAR! NEQ 0 SET OUTSTR=!OUTSTR!%%S
IF !STRPTR! NEQ 0 IF !PAR! NEQ 0 SET OUTSTR=!OUTSTR!^</dd^>
IF !STRPTR! NEQ 0 IF !PAR! EQU 0 SET OUTSTR=!OUTSTR!^<dt class="prspec"^>
IF !STRPTR! NEQ 0 IF !PAR! EQU 0 SET OUTSTR=!OUTSTR!%%S
IF !STRPTR! NEQ 0 IF !PAR! EQU 0 SET OUTSTR=!OUTSTR!^</dt^>
IF !STRPTR! NEQ 0 SET /A COUNT=!COUNT!+1
SET /A STRCOUNT=!STRCOUNT!-1
IF !STRCOUNT! EQU 0 SET OUTSTR=!OUTSTR!^</dl^>^" && ECHO !OUTSTR!>>!OUTFLNM! && GOTO FORMEND
CHCP 866
)
:FORMEND
Вот мы и получили всё, что хотели.
Как видим, не сильно сложно. Самое смешное во всём этом даже не cmd, а то, что если купить какой-нибудь готовый парсер, то делать придётся практически то же самое. Всё равно ковырять страницу, выискивать теги, запрашивать ссылки. А если у нашего маркета что-то поменяется, то нет гарантии, что платный парсер с закрытым кодом не перестанет работать. В нашем же случае изменения внести проще простого, даже компилятор не нужен.
Защита от парсинга на market'ах, в общем, номинальная. Открыли бы API с ограниченными запросами, проблем было бы меньше для них же.
Хочется загрузить полученную информацию куда-нибудь в базу данных или на сайт? Да не вопрос. Переведём в xml, csv, sql и вообще что угодно. Но об этом – в следующий раз.
Да пребудет с вами Regex, да обойдёт вас капча!
Комментариев нет:
Отправить комментарий