Для контент-ориентированных компаний важна безопасность данных. Для контент-компаний важность данных невозможно переоценить. Например, если вы являетесь платформой для онлайн-обучения, данные темы очень важны, но они были украдены другими с помощью технологии сканирования? Если убрать основную конкурентоспособность, будет круто. Другой пример: независимый разработчик хочет скопировать ваш продукт, забрать ваши основные данные с помощью захвата пакетов и сканирования, а затем создать веб-сайт и приложение за короткий период времени и стать вашим сильным врагом в краткосрочной перспективе.
рептилия означает
В настоящее время технология сканирования напрямую находит интересующий узел на отображаемой html-странице, а затем получает соответствующий текст.
Некоторые веб-сайты хорошо защищены. Например, страницу списка может быть легко получить, но на странице сведений необходимо щелкнуть соответствующий элемент на странице списка, отправить itemId через форму формы, сервер генерирует соответствующие параметры и затем перенаправляет на страницу сведений (повторно после того, как направленный адрес приходит с параметром detailID страницы сведений), этот шаг может перехватить некоторых разработчиков сканеров
упражнятьсяТехническое решение для защиты от сканирования на стороне веб-сайта
С этих двух точек зрения (то, что вы видите на веб-странице, — это не то, что вы получаете, и бесполезно проверять интерфейсный запрос), я сформулировал следующий план защиты от сканирования.
Использовать HTTPS-протокол
Слишком много запросов за единицу времени, учетная запись блокируется
Ограничения передних технологий (следующая базовая технология)
Серверной части необходимо зашифровать данные в соответствии с протоколом, разработанным на предыдущем шаге.
Ниже сNode.jsВ качестве примера, чтобы объяснить, что должен делать бэкэнд
Сначала установите маршрутизацию интерфейса на бэкэнде
Получить параметры за маршрутом
Создавайте соответствующие данные на основе операторов SQL в соответствии с потребностями бизнеса. Если это цифровая часть, ее необходимо преобразовать в соответствии с методом, согласованным выше.
Преобразуйте сгенерированные данные в JSON и верните их вызывающей стороне
// json
var JoinOparatorSymbol = "3.1415926";
function encode(rawData, ruleType) {
if (!isNotEmptyStr(rawData)) {
return "";
}
var date = new Date();
var year = date.getFullYear();
var month = date.getMonth() + 1;
var day = date.getDate();
var encodeData = "";
for (var index = 0; index < rawData.length; index++) {
var datacomponent = rawData[index];
if (!isNaN(datacomponent)) {
if (ruleType < 3) {
var currentNumber = rawDataMap(String(datacomponent), ruleType);
encodeData += (currentNumber * month + day) + JoinOparatorSymbol;
}
else if (ruleType == 4) {
encodeData += rawDataMap(String(datacomponent), ruleType);
}
else {
encodeData += rawDataMap(String(datacomponent), ruleType) + JoinOparatorSymbol;
}
}
else if (ruleType == 4) {
encodeData += rawDataMap(String(datacomponent), ruleType);
}
}
if (encodeData.length >= JoinOparatorSymbol.length) {
var lastTwoString = encodeData.substring(encodeData.length - JoinOparatorSymbol.length, encodeData.length);
if (lastTwoString == JoinOparatorSymbol) {
encodeData = encodeData.substring(0, encodeData.length - JoinOparatorSymbol.length);
}
}
//字体映射处理
function rawDataMap(rawData, ruleType) {
if (!isNotEmptyStr(rawData) || !isNotEmptyStr(ruleType)) {
return;
}
var mapData;
var rawNumber = parseInt(rawData);
var ruleTypeNumber = parseInt(ruleType);
if (!isNaN(rawData)) {
lastNumberCategory = ruleTypeNumber;
//字体文件1下的数据加密规则
if (ruleTypeNumber == 1) {
if (rawNumber == 1) {
mapData = 1;
}
else if (rawNumber == 2) {
mapData = 2;
}
else if (rawNumber == 3) {
mapData = 4;
}
else if (rawNumber == 4) {
mapData = 5;
}
else if (rawNumber == 5) {
mapData = 3;
}
else if (rawNumber == 6) {
mapData = 8;
}
else if (rawNumber == 7) {
mapData = 6;
}
else if (rawNumber == 8) {
mapData = 9;
}
else if (rawNumber == 9) {
mapData = 7;
}
else if (rawNumber == 0) {
mapData = 0;
}
}
//字体文件2下的数据加密规则
else if (ruleTypeNumber == 0) {
if (rawNumber == 1) {
mapData = 4;
}
else if (rawNumber == 2) {
mapData = 2;
}
else if (rawNumber == 3) {
mapData = 3;
}
else if (rawNumber == 4) {
mapData = 1;
}
else if (rawNumber == 5) {
mapData = 8;
}
else if (rawNumber == 6) {
mapData = 5;
}
else if (rawNumber == 7) {
mapData = 6;
}
else if (rawNumber == 8) {
mapData = 7;
}
else if (rawNumber == 9) {
mapData = 9;
}
else if (rawNumber == 0) {
mapData = 0;
}
}
//字体文件3下的数据加密规则
else if (ruleTypeNumber == 2) {
if (rawNumber == 1) {
mapData = 6;
}
else if (rawNumber == 2) {
mapData = 2;
}
else if (rawNumber == 3) {
mapData = 1;
}
else if (rawNumber == 4) {
mapData = 3;
}
else if (rawNumber == 5) {
mapData = 4;
}
else if (rawNumber == 6) {
mapData = 8;
}
else if (rawNumber == 7) {
mapData = 3;
}
else if (rawNumber == 8) {
mapData = 7;
}
else if (rawNumber == 9) {
mapData = 9;
}
else if (rawNumber == 0) {
mapData = 0;
}
}
else if (ruleTypeNumber == 3) {
if (rawNumber == 1) {
mapData = "";
}
else if (rawNumber == 2) {
mapData = "";
}
else if (rawNumber == 3) {
mapData = "";
}
else if (rawNumber == 4) {
mapData = "";
}
else if (rawNumber == 5) {
mapData = "";
}
else if (rawNumber == 6) {
mapData = "";
}
else if (rawNumber == 7) {
mapData = "";
}
else if (rawNumber == 8) {
mapData = "";
}
else if (rawNumber == 9) {
mapData = "";
}
else if (rawNumber == 0) {
mapData = "";
}
}
else{
mapData = rawNumber;
}
} else if (ruleTypeNumber == 4) {
var sources = ["年", "万", "业", "人", "信", "元", "千", "司", "州", "资", "造", "钱"];
//判断字符串为汉字
if (/^[\u4e00-\u9fa5]*$/.test(rawData)) {
if (sources.indexOf(rawData) > -1) {
var currentChineseHexcod = rawData.charCodeAt(0).toString(16);
var lastCompoent;
var mapComponetnt;
var numbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
var characters = ["a", "b", "c", "d", "e", "f", "g", "h", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
if (currentChineseHexcod.length == 4) {
lastCompoent = currentChineseHexcod.substr(3, 1);
var locationInComponents = 0;
if (/[0-9]/.test(lastCompoent)) {
locationInComponents = numbers.indexOf(lastCompoent);
mapComponetnt = numbers[(locationInComponents + 1) % 10];
}
else if (/[a-z]/.test(lastCompoent)) {
locationInComponents = characters.indexOf(lastCompoent);
mapComponetnt = characters[(locationInComponents + 1) % 26];
}
mapData = "&#x" + currentChineseHexcod.substr(0, 3) + mapComponetnt + ";";
}
} else {
mapData = rawData;
}
}
else if (/[0-9]/.test(rawData)) {
mapData = rawDataMap(rawData, 2);
}
else {
mapData = rawData;
}
}
return mapData;
}
//路由
const fs = require("fs");
function addMapping(router, mapping){
for(var url in mapping){
if (url.startsWith("GET")) {
var path = url.substring(4);
router.get(path,mapping[url]);
console.log(`Register URL mapping: GET: ${path}`);
}else if (url.startsWith('POST ')) {
var path = url.substring(5);
router.post(path, mapping[url]);
console.log(`Register URL mapping: POST ${path}`);
} else if (url.startsWith('PUT ')) {
var path = url.substring(4);
router.put(path, mapping[url]);
console.log(`Register URL mapping: PUT ${path}`);
} else if (url.startsWith('DELETE ')) {
var path = url.substring(7);
router.del(path, mapping[url]);
console.log(`Register URL mapping: DELETE ${path}`);
} else {
console.log(`Invalid URL: ${url}`);
}
}
}
function addControllers(router, dir){
fs.readdirSync(__dirname + "/" + dir).filter( (f) => {
return f.endsWith(".js");
}).forEach( (f) => {
console.log(`Process controllers:${f}...`);
let mapping = require(__dirname + "/" + dir + "/" + f);
addMapping(router,mapping);
});
}
module.exports = function(dir){
let controllers = dir || "controller";
let router = require("koa-router")();
addControllers(router,controllers);
return router.routes();
};
Внешний интерфейс расшифровывает данные в обратном порядке в соответствии с данными, возвращаемыми сервером.
$("#year").html(getRawData(data.year,log));
// util.js
var JoinOparatorSymbol = "3.1415926";
function isNotEmptyStr($str) {
if (String($str) == "" || $str == undefined || $str == null || $str == "null") {
return false;
}
return true;
}
function getRawData($json,analisys) {
$json = $json.toString();
if (!isNotEmptyStr($json)) {
return;
}
var date= new Date();
var year = date.getFullYear();
var month = date.getMonth() + 1;
var day = date.getDate();
var datacomponents = $json.split(JoinOparatorSymbol);
var orginalMessage = "";
for(var index = 0;index < datacomponents.length;index++){
var datacomponent = datacomponents[index];
if (!isNaN(datacomponent) && analisys < 3){
var currentNumber = parseInt(datacomponent);
orginalMessage += (currentNumber - day)/month;
}
else if(analisys == 3){
orginalMessage += datacomponent;
}
else{
//其他情况待续,本 Demo 根据本人在研究反爬方面的技术并实践后持续更新
}
}
return orginalMessage;
}
Например, бэкенд возвращает 323.14743.14743.1446, по нашему согласованному алгоритму результат может быть 1773
Отрисовка страницы в соответствии с файлом ttf
Вычисленный выше 1773, а то по ttf файлу страница видит 1995
Затем, чтобы сканеры не могли просматривать проблемы исследования JS, файлы JS шифруются. Если ваш технологический стек Vue, React и т. д., webpack предоставляет вам плагин для шифрования JS, который также очень удобен в обращении.
Лично я не считаю этот метод очень безопасным. Поэтому я подумал о комбинации различных планов. Например
Обновленная версия защиты от скалолазания
Лично я чувствую, что если дело касается опытного разработчика краулеров, вышеприведенное решение все еще может быть взломано, поэтому обновленная версия делается на основе предыдущей.
Комбинация 1: файл шрифта не должен быть исправлен.Хотя запрошенная ссылка та же самая,модуль берется в соответствии с последним числом текущей метки времени.Например,модуль 4 берется в Демо, а есть 4 значения 0, 1, 2 и 3. Эти 4 значения соответствуют разным файлам шрифтов, поэтому, когда краулер ломал голову, чтобы доползти до шрифта в одном случае, он не ожидал запросить его повторно, и правила файла шрифта изменились 😂
Комбинация 2: Предыдущее правило заключается в том, что проблема со шрифтом не в порядке, но нарушается только соответствие чисел. Например1 -> 4, 5 -> 8. Следующая процедура заключается в том, что каждое число соответствуетЮникод кодЗатем нужно сделать шрифт можно .ttf, .woff и так далее.
Такие комбинации сбивают с толку. Для обычных рептилий он заброшен.
Противозалезающие средства для апгрейда
Перечисленные выше методы в основном направлены наномерЧто мне делать, если я хочу отменить восхождение китайских иероглифов? Далее несколько вариантов
план 1:Для облака слов с наибольшей частотой на вашем сайте сделайте карту китайских иероглифов, то есть файл пользовательского шрифта, шаги такие же, как и числа. Сначала создайте соответствующие файлы ttf для часто используемых китайских иероглифов; преобразуйте файлы ttf в файлы svg по приведенным ниже ссылкам, а затем выберите ранее созданные файлы svg на веб-сайте, где щелкнут ссылку «сопоставление шрифтов» ниже, и поместите файлы svg в файл svg.Сделайте сопоставление для каждого китайского символа , то есть китайский символ является специальным кодом юникода (обратите внимание, что код юникода здесь не должен быть напрямую сгенерирован онлайн, потому что напрямую сгенерированные вещи также Метод, который я даю, заключается в том, чтобы сначала использовать веб-сайт для его создания, а затем внести простое изменение в полученный результат, например, преобразовать «e342» в «e231»); затем данные, возвращаемые интерфейсом, отображаются в обратном порядке в соответствии с к правилам нашего файла шрифта.
Сценарий 2:Важные шрифты веб-сайта генерируются в изображения из html-части, поэтому сканеру необходимо идентифицировать требуемый контент с большими затратами и использовать OCR. КПД тоже низкий. Таким образом, вы можете перехватить некоторые сканеры
Сценарий 3:См. Обмен технологиями Ctrip «Наивысший уровень защиты от лазания — это отпечаток Canvas. Принцип заключается в том, что разные машины и разное оборудование всегда имеют ошибки на уровне пикселей в картинках, нарисованных Canvas. Поэтому мы судим, что когда большое количество холсты обращаются для доступа, всегда есть ошибки на уровне пикселей.Если отпечатки пальцев совпадают, это считается рептилией, и ее можно заблокировать».
Я реализовал решение 1 в демо.
ключевой шаг
Сначала найдите часто используемые ключевые слова в соответствии с вашими продуктами, сгенерируйтеоблако слов
В соответствии с облаком слов сгенерируйте соответствующий код юникода для каждого слова.
Превратите китайские иероглифы, включенные в облако слов, в библиотеку шрифтов.
Сделайте библиотеку шрифтов .ttf в формате svg и загрузите ее наicomoonСоздавайте пользовательские шрифты, но с правилами, например"год"соответствующийюникод кодда">74", но нам нужно сделатьШифрование Цезаря, например, мы устанавливаемКомпенсироватьравно 1, то послеШифрование Цезаря"год"соответствующийunicodeкод">75". Используйте это правило, чтобы сделать нужную нам библиотеку шрифтов
Что сервер делает каждый раз, когда вызывается интерфейс: сервер инкапсулирует метод и оценивает, находятся ли данные в облаке слов с помощью метода Если это символ в облаке слов, используйте правила (найдите код юникода соответствующий китайскому иероглифу) , а затем установить соответствующее смещение по шифрованию Цезаря, 1 в Демо, зашифровать каждый китайский иероглиф) и вернуть данные после шифрования
Что делает клиент:
Сначала импортируйте библиотеку шрифтов китайских иероглифов, которую мы создали ранее.
Вызовите интерфейс, чтобы получить данные и отобразить их на соответствующем узле Dom.
Если это китайский текст, мы устанавливаем класс css соответствующего узла в класс китайских символов, а семейство шрифтов, соответствующее этому классу, представляет собой библиотеку шрифтов китайских символов, которую мы представили выше.
Данные, отображаемые на странице, не соответствуют результатам, отображаемым элементом проверки.
Просмотр данных интерфейса не соответствует элементам аудита и увиденному интерфейсу
Более противоречивые результаты перед каждым обновлением страницы
Методы обработки чисел и китайских иероглифов несовместимы.
Такие комбинации сбивают с толку. Для обычных рептилий он заброшен.
Предыдущий веб-сайт ttf to svg будет ограничивать преобразование, когда файл ttf слишком велик, и позволит вам купить его.Новая ссылка будет размещена ниже.
//客户端。先查看本机 ip 在 Demo/Spider-develop/Solution/Solution1.js 和 Demo/Spider-develop/Solution/Solution2.js 里面将接口地址修改为本机 ip
$ cd Demo
$ ls
REST Spider-release file-Server.js
Spider-develop Util rule.json
$ node file-Server.js
Server is runnig at http://127.0.0.1:8080/
//服务端 先安装依赖
$ cd REST/
$ npm install
$ node app.js