Не могу спарсить данные с сайта
Нужно спарсить данные автомобиля с сайта, подставив в поиск его vin-код. И тут начинается самое интересное, на решение которого было потрачено уже почти 4 дня. Решение обязательно должно быть при использовании aiohttp, или requests/requests_html/scrapy.
selenium не подходит!
UPD3. (проблемы №1 и №2 решены, можно перейти сразу к номеру 3)
Проблема номер 1:
при GET-запросе я получаю сообщение о том, что нужно включить JavaScript. Ранее подобной проблемы при парсинге сайта не было, и мне удавалось получить html главной станицы сайта.
Что можно предпринять? Облазил почти все форумы - нашел только советы недопрограммистов, типа "так включи JS и будет работать".
Страница сайта, которую получаю:

Там есть какие-то ключи для декодирования, slowAES.decrypt и сам JS-код. Может как-то можно воспроизвести логику в python, чтобы это ограничение обойти?
Проблема номер 2:
Я смотрел в инструментах разработчика - какие передаются данные для поиска:
обычный GET-запрос, и токен, который генерируется из 463 символов:
Я в общем не стал заморачиваться, чтоб переписать функцию на python, и воспользовался js2py, которая генерирует этот токен:
getToken = js2py.eval_js("""
function randomString(len) {
var str = "";
for (var i = 0; i < len; i++) {
var rand = Math.floor(Math.random() * 62);
var charCode = rand += rand > 9 ? (rand < 36 ? 55 : 61) : 48;
str += String.fromCharCode(charCode);
}
return str;
}
""")
token2 = getToken(463)
print(token2)
Но проблема оказалось с тем, что при передаче всех этих данных - поиск не работает, на этом сайте стоит под защитой невидимая recaptcha.
Я пошел дальше, и проанализировал их код сайта. Нашел такую функцию, которая делает запрос напрямую к серверу (php-скрипт):
function dle_do_search(a) {
clearInterval(dle_search_delay);
$("#searchsuggestions").remove();
$("body").append("<div id='searchsuggestions' style='display:none'></div>");
$.post(dle_root + "engine/ajax/controller.php?mod=search", {
query: "" + a,
user_hash: dle_login_hash
}, function(a) {
$("#searchsuggestions").html(a).fadeIn().css({
position: "absolute",
top: 0,
left: 0
}).position({
my: "left top",
at: "left bottom",
of: "#story",
collision: "fit flip"
})
});
В данном случае a - это передаваемое значение, то есть vin-код автомобиля.
dle_login_hash - я так полагаю, что это хэш сессии. Проанализировав запрос в инструментах разработчика я нашел такую переменную:
var dle_login_hash = 'fcab770449489970ce5bb3652037c19f7f24e73';
Я попытался воспроизвести POST-запрос на их сервер, но получил ошибку 403 (работало до тех пор, пока не появилась проблема номер 1). Возможно я упустил какой-то токен, или sessionid при передаче, но в функции и нет его, которую я привел выше.
Мой код:
import asyncio
import aiohttp
import json
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/94.0.4606.71 Safari/537.36",
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,"
"application/signed-exchange;v=b3;q=0.9",
}
data = {
"query": "5YJ3E1EA0JF042788",
"user_hash": "fcab770449489970ce5bb3652037c19f7f24e73"
}
async def parse_vin_carsvin():
url = f"https://bidfax.info/engine/ajax/controller.php?mod=search"
async with aiohttp.ClientSession(headers=headers) as session:
async with session.post(url, data=json.dumps(data)) as response:
result = await response.text()
print(result)
loop = asyncio.get_event_loop()
loop.run_until_complete(parse_vin_carsvin())
UPD. Получилось воссоздать логику, но это только напрямую, если скопировать все из инспектора. Проверил - для корректной работы нужен fort и searchkey.
UPD2.
С генерацией ключа FORT разобрался, остался только searchkey.
Пока что не удалось разобраться, как он генерируется.
UPD3.
Проблема номер 3:
Вполне вероятно, что searchkey формируется одной из функций, которая приведена на SlowAES
Там есть такая функция toHex(generatePrivateKey("сюда передается какой-то ключ", 16)); - вполне вероятно, она отвечает за его генерацию, но непонятно что нужно передать за ключ. Пробовал передавать и phpsessid и fort - безрезультатно. Может отвечает функция toHex(slowAES.decrypt(c,2,a,b)); которая генерирует FORT, но я перебрал все возможные 2401 комбинаций (7^4 = 2401; есть значения a, b, c, phpsessid, fort, 1, 2). По факту нужно было меньше, так как оказалось что 2 это режим для декодирования aes).
Ответы (3 шт):
UPD
Пока разбирался с Selenium в качестве 'подопытного' взял данный сайт.
Вот мой код, который сохраняет куки в бинарник и ищет VIN машины.
def selenium_parser(url , driver):
driver.get(url)
search_input = driver.find_element_by_id('search')
search_input.clear()
search_input.send_keys('5YJ3E1EA0JF042788')
submit_btn = driver.find_element_by_id('submit')
submit_btn.click()
time.sleep(5)
def getDataSelenium(url,parser):
driver = webdriver.Chrome(
executable_path='./browsers/chromedriver/chromedriver',
seleniumwire_options=settings.SELENIUM_PROXY
)
try:
cookie_name = f"{url.split('//')[-1]}_cookies"
if os.path.exists(cookie_name):
print('Берем куки с файла')
for cookie in pickle.load(open(cookie_name , 'rb')):
driver.add_cookie(cookie)
parser(url , driver)
except Exception as ex:
print(ex)
finally:
print('Сохраняем куки')
pickle.dump(driver.get_cookies() , open( cookie_name , 'wb'))
driver.close()
driver.quit()
def main():
getDataSelenium('https://bidfax.info', selenium_parser)
if __name__ == "__main__":
main()
Старый ответ:
Если выкинуть весь хлам то вот что получаю я:
<div class="col-xs-12 col-sm-6">
<div class="thumbnail offer">
<div class="img-wrapper">
<a href="https://bidfax.info/tesla/model-3/10567477-tesla-model-3-range-battery-2018-gray-motor-vin-5yj3e1ea0jf042788.html">
<img class="xfieldimage skrin" src="/uploads/posts/2021-10/08/tesla-model-3-2018-5yj3e1ea0jf042788-img1.jpg" alt="">
</a>
</div>
<div class="price"><i class="fa fa-dollar"></i><span class="prices" id="prices-10567477">26575</span></div>
<div class="caption">
<a href="https://bidfax.info/tesla/model-3/10567477-tesla-model-3-range-battery-2018-gray-motor-vin-5yj3e1ea0jf042788.html"><h2>Tesla Model 3 Range Battery 2018 Gray MOTOR vin: 5YJ3E1EA0JF042788</h2></a>
<p class="short-storyup">Аукцион: <span class="iaai"> IAAI </span> <img src="/templates/ru/dleimages/onapproval2.png" alt="На утверждении" title="На утверждении" style="margin-top:-4px"></p>
<p class="short-story">Номер лота: <span class="blackfont">31321550</span></p>
<p class="short-story2">Состояние: <span class="blackfont">Run and Drive</span></p>
<p class="short-story">Повреждение: <span class="blackfont">Flood</span></p>
<p class="short-story2">Пробег: <span class="blackfont">9360 миль (Actual)</span></p>
<p class="short-story">Дата продажи: <span class="blackfont">07.10.2021</span></p>
<div class="clearfix">
<a href="https://bidfax.info/tesla/model-3/10567477-tesla-model-3-range-battery-2018-gray-motor-vin-5yj3e1ea0jf042788.html" class="btn btn-default btn-more pull-right" role="button">Подробнее</a>
</div>
</div>
</div>
</div>
</div>
</div>
Сделал обычным requests. Все данные для запроса перехватил через burpsuite и добавил в запрос
адрес запроса выглядит примерно так:
https://bidfax.info/?do=search&subaction=search&story=5YJ3E1EA0JF042788&token2=03AGdBq27XAie48A7sAG9gqugwLVvp9Ve5m9DpwyvhRviVHQy_iCZXdl-Rf2UH-2oFGpINU7ZMHMOZCXENBIwwgzEmy6w1kGV8qrGt-sOG0pDRYWYJTSDq7H1VvA7ZGMPmIu37AI3JebUBAquXdU-H090ubYA3j6JZ-M52YWKTWM1mG6rX-w1TkgDottqIf5P03mlM5nOoU_9ft0D6usirRDNSr8JFK8aEpYvba7fdPwGiIZg-B8H6OlZpTXxJyvsHXCkWUpvQXgsq4QIXmCKXiVoMUgvBvFWE_pgeqSIpFIaddHK50gqPFEcWLFrxbyUOiJWKA_Sjkay8wJqfLbo3b8kB1SIhG2XdjdknRnADqB32j0qmA2ZSoynEW2s9sNmz76VUI861RbOOyhDi_Q4HgJrcNeEf6N3qWYGPXbjXt2uSA547u__tWdwGy93WeGRihRZEntvemDQVzFOwHe1v4DotsQ&action2=search_action
Изначальные заголовки:
Может неправильно понял вопрос, не знаю, если что пишите
UPD:
Удалил куки с браузера и через Burpsuite посмотрел этапы прохождения капчи и соедниения для главной страницы. И там тоже возвращается, что нужно включить js, просто видимо это первый этап

Я думаю вам стоит изучить последовательность этих запросов и попытаться их воспроизвести:
Первый шаг голый запрос без кук, получаем script и ссылка на aes.js
Загружаем этот aes.js и видимо исполняем скрипт
var a=toNumbers("b0bd65ce35df0ebd05a1c3fa2647308f"), b=toNumbers("a89b6850a385bcf6f3a8f3514e1b16a4"), c=toNumbers("f4f9556a8771fea9632da685e849382b"); document.cookie="FORT="+toHex(slowAES.decrypt(c,2,a,b))+"; expires=Thu, 31-Dec-37 23:55:55 GMT; path=/"; location.href="https://bidfax.info/?fortespro_42=1";
(это кусок кода из того, что вам возвращается с сообщение включите js, данное сообщение будет отображаться в браузере и не будет запущен код js, который инициирует дальнейшие действияя)
В этом коде на фронте формируется "FORT"
Когда делаем третий запрос у нас уже есть в куках FORT
на этом шаге видно, как мы отсылаем FORT и получаем PHPSSESID(тут мы уже получаем страницу, которую можно парсить, но дальше есть еще куча всего)
Конечный вывод: надо скопировать js код себе, так как он статичен, с его помощью и формировать "FORT" самому и отдавать его обратно в python для запросов
Еще один дополнительный совет, как видно из изображения данная кука формируется на 15 лет, думаю не стоит сильно парится, а searchkey могу предположить формируется именно на 'FORT'(надо проверить), и меняться не будет пока FORT не поменяется, хоть searchkey и пересоздается каждый раз он всегда у меня одинаковый
UPD: Доказательство по поводу searchkey
В общем мне так и не удалось решить проблему - я застрял на генерации ключа searchkey и думаю что на этом история и закончится. Пробовал уже и selenium и pyppeteer чтоб только забирать этот ключ и уже автоматизировать процесс, но они тоже не работают должным образом.
Я написал разработчику этого сайта, чтобы узнать как они генерируют этот ключ, но мне ничего не ответили и благополучно заблокировали)) Вероятно, подумали, что я хочу увести их клиентов.
Если вы знаете, как генерируется ключ исходя из кода SlowAES - дайте мне знать об этом в комментариях.
Итог всего этого и подсказки для тех, кому это может показаться полезным:
1) Чтобы спарить данные (именно этого сайта) - нужны 2 ключа: FORT и searchkey
Передаваемые ключи можно смотреть в cookie/set-cookie при запросе к сайту в инспекторе браузера.
FORT - ключ подтверждения, что был включен JavaScript;
searchkey - ключ, который разрешает поиск на сайте.
2) JavaScript можно включить, если парсить через requests/aiohttp
Чтоб включить JavaScript, когда нужно спарсить данные через aiohttp/requests - вам нужно выбрать скрипт из тегов <script>Сам скрипт</script> и декодировать сгенерированные хэши, которые приведены на странице. Самым простым способом будет воспользоваться библиотекой js2py которая обрабатывает JavaScript-код внутри интерпретатора языка Python (таким образом не нужно будет писать дополнительные файлы с *.js, но надо будет подтянуть var slowAES = ... для корректной работы декодирования - в коде ниже есть пример)
3) Чтоб спарсить данные - ключ можно вручную забрать из инспектора.
Чтобы парсить именно этот сайт - нужно забрать ключ searchkey с инспектора, и передать его вместе с FORT, иначе сайт будет редиректить на главную страницу.
Подобная логика может прослеживаться и на других сайтах. Сайт спустя какое-то время заново генерирует ключ searchkey - в этом случае процедуру нужно повторить.
Сам код:
from bs4 import BeautifulSoup
import aiohttp
import asyncio
import js2py
headers = {
'Host': 'bidfax.info',
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/94.0.4606.71 Safari/537.36",
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,"
"application/signed-exchange;v=b3;q=0.9",
"Accept-Language": "accept-language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7",
"Accept-Encoding": "gzip, deflate, br",
"DNT": "1",
"Connection": "close",
"Upgrade-Insecure-Requests": "1",
"Cache-Control": "max-age=0",
}
token2 = js2py.eval_js("""
function randomString(len) {
var str = "";
for (var i = 0; i < len; i++) {
var rand = Math.floor(Math.random() * 62);
var charCode = rand += rand > 9 ? (rand < 36 ? 55 : 61) : 48;
str += String.fromCharCode(charCode);
}
return str;
}
""")
async def test(vin):
url = "https://bidfax.info/"
aes_js = "https://bidfax.info/aes.min.js"
parse_url = f"https://bidfax.info/?do=search&subaction=search&story={vin}&token2={token2(463)}&action2=search_action"
async with aiohttp.ClientSession(headers=headers) as session:
async with session.get(url) as response:
script = BeautifulSoup(await response.text(), "html.parser").find_all("script")[1].get_text()
response = await session.get(aes_js)
slowAES = await response.text()
jslogic = script.split('document.cookie="')[0]
FORT = js2py.eval_js(slowAES + jslogic + " toHex(slowAES.decrypt(c,2,a,b));")
searchkey = "" # сюда нужно вставить searchkey
session.headers.update({"Cookie": "FORT=" + FORT + "; searchkey=" + searchkey})
res = await session.get(parse_url)
with open("file.html", "w", encoding="UTF-8") as file:
file.write(await res.text())
loop = asyncio.get_event_loop()
loop.run_until_complete(test("5YJ3E1EA0JF042788"))
Отдельное спасибо @Alexandr за подсказки на некоторых этапах!





