Системный Обзор
-
Мониторинг каналов направлен на сбор информации, связанной с приложением, путем сканирования различных каналов, онлайн-дисков, форумов, почтовых ящиков и т. д., выявления подлинного пиратства посредством анализа полученной информации и выпуска отчетов статистического анализа, чтобы помочь разработчикам приложений отслеживать рынок приложений, Позитивное пиратство на постах, форумах, онлайн-дисках и т. д.
-
Ниже приведены конкретные функциональные точки:
- Загрузите приложение в фоновом режиме, чтобы отправить систему мониторинга канала.
- После того, как загруженное приложение получено в фоновом режиме, приложение обрабатывается для получения информации о подписи приложения, имени приложения, имени пакета приложения, структуры файла приложения и другой информации.
- В соответствии с полученной информацией о приложениях и технологией поискового робота Интернет-каналы сканируются в фиксированных точках.
- Сканирование в режиме реального времени 428 внутренних рынков приложений, онлайн-дисков, 70 форумов и сообществ разработчиков, а также 78 сообщений, связанных с безопасностью клиентов. (Улучшение...)
- Единое хранилище просканированных данных.
- Выполняйте одноключевой анализ обнаруженных подозрительных пиратских приложений и обнаруживайте вредоносный код, внедренный подозрительными пиратскими приложениями, измененными ресурсами и т. д. (Завершение...)
- Поддержка удаления из списка обнаруженных подозреваемых пиратских приложений. (План дальнейших действий)
- Предоставьте отзыв о данных отслеживаемого канала, обнаруженных подозрительных данных пиратского приложения, а также о процессе работы и документе формирования результатов предполагаемой пиратской версии.
Функция постепенно улучшается...
-
Во вложении рендеринг:
визуализация
карта разума
Интеллект-карта мониторинга каналовКрасная рамка, обведенная выше, — это поисковая система bbs, которую я хочу представить вам сегодня.
Внедрение краулерной системы BBS
Система сканирования bbs основана на открытом исходном коде на github.pyspiderКраулерная система реализована, количество запусков превысило 10 000, а количество форков превысило 2 600. Также относительно качественный проект с открытым исходным кодом. Позвольте мне сначала кратко представить функциональные точки и архитектуру pyspider:
Знакомство с пауком
Знакомство с паукомСкриншот выше взят из введения в документацию официального сайта pyspider. Pyspider написан на языке python, поддерживает онлайн-редактирование и отладку скриптов сканера в визуальном интерфейсе, поддерживает мониторинг задач сканера в режиме реального времени, поддерживает распределенное развертывание и поддерживает общие базы данных, такие как mysql, sqlite, ES и MongoDB для хранения данных. . Его распределенное развертывание реализуется с помощью промежуточного программного обеспечения сообщений, а промежуточное программное обеспечение, такое как redis и rabbitmq, может использоваться в качестве промежуточного программного обеспечения для сообщений распределенного развертывания. Кроме того, он также поддерживает повторные попытки выполнения задач сканера, ограничение скорости через ведро токенов, управляемый приоритет задач, время истечения срока действия задач сканера и т. д. Многие из его функций - это то, что должно быть в краулерной системе общего назначения.Если мы хотим написать краулерную систему с нуля, мы должны решить эти проблемы, что требует много времени и труда.Лучше стоять на плечи гигантов и превратить его в нечто подходящее для нашей орбиты колесо.
Архитектурный дизайн Pyspider
- Искатель pyspider можно разделить на следующие основные компоненты:
-
Fetcher - сканирование интернет-ресурсов на основе URL-адресов и загрузка html-контента. Fetcher реализуется асинхронным вводом-выводом, который может поддерживать большое количество одновременных выборок.Узким местом в основном являются накладные расходы ввода-вывода и ресурсы IP. Если один ip-адрес сканируется слишком быстро, его легко задеть системой защиты от сканирования bbs, и его легко заблокировать.Вы можете использовать пул ip-прокси, чтобы решить проблему ограничений ip. Если пула IP-прокси нет, можно использовать механизм ограничения скорости pyspider, чтобы предотвратить прикосновение к механизму антисканирования. Поддерживается многоузловое развертывание.
-
Процессор — обрабатывать наши скрипты сканера. Например, извлечение ссылок на страницах, извлечение ссылок для перелистывания страниц, извлечение подробной информации со страниц и т. д. потребляют ресурсы ЦП. Поддерживается многоузловое развертывание.
-
WebUI — визуальный интерфейс, который поддерживает онлайн-редактирование и отладку сценариев сканера; поддерживает онлайн-мониторинг задач сканера в режиме реального времени; может запускать, останавливать, удалять и ограничивать скорость задач сканера через интерфейс. Поддерживается многоузловое развертывание.
-
Планировщик — компонент запланированного задания. Каждый URL-адрес соответствует задаче, планировщик отвечает за распределение задач, хранение новых URL-адресов и координацию различных компонентов через очередь сообщений. Может быть развернут только на одном узле.
-
ResultWorker — компонент записи результатов. Поддерживает пользовательскую реализацию результатов поискового робота. Например, мы внедрили запись пользовательских результатов на основе RDS. Можно не реализовывать, по умолчанию в качестве вывода результата используется sqlite, который можно экспортировать в execel, json и другие форматы.
-
Диаграмма архитектуры
Архитектура паука -
Веб-интерфейс pyspider выглядит так
- Сценарий, который мы собираемся написать
from pyspider.libs.base_handler import *
class Handler(BaseHandler):
# 全局设定,遇到反爬的情况需要在其中配置对应的措施(如代理,UA,Cookies,渲染.......),
crawl_config = {
}
@every(minutes=24 * 60)
def on_start(self):
# seed urls
self.crawl('http://scrapy.org/', callback=self.index_page)
@config(age=10 * 24 * 60 * 60)
def index_page(self, response):
for each in response.doc('a[href^="http"]').items():
self.crawl(each.attr.href, callback=self.detail_page)
def detail_page(self, response):
# result
return {
"url": response.url,
"title": response.doc('title').text(),
}
Для получения дополнительной документации по Pyspider вы можете обратиться к: документация pyspider
Применение Pyspider в системе Crawler BBS
блок-схемаШаги блок-схемы объясняются следующим образом:
-
Шаг 1-1: Пользователь пишет сценарий через интерфейс WebUI, отлаживает и сохраняет его, а затем нажимает «Выполнить», чтобы запустить сценарий сканера. WebUI вызовет метод планировщика new_task через xmlrpc, чтобы создать новую задачу сканера. (Этот шаг инкапсулирует процесс создания скрипта pyspider через java и реализует автоматическое создание скриптов сканера в соответствии с apk, загруженным пользователем)
-
Шаг 2: После того, как планировщик получит задачу сканера (определение json_string) через xmlrpc, он начнет некоторую обработку обновления статуса проекта, приоритета обновления, времени обновления и т. д. После обработки этой логики отправьте URL сборщику через метод send_task.
-
Шаги 3 и 4: сборщик получает html-страницу через асинхронный ввод-вывод, а затем отправляет результат процессору.
-
Шаг 5: После того, как процессор получает html-страницу, отправленную сборщиком, он вызывает пользовательский метод index_page. Получите гиперссылки на странице в методе index_page и вызовите метод detial_page. Процессор отправляет проанализированный результат в ResultWorker (очередь на основе реализации списка NCR).
-
Шаги 6 и 7: Компонент ResultWorker получает результат от NCR и записывает его в базу данных RDS.
-
Шаг 1-2: В дополнение к извлечению сведений о нужной странице, index_page в процессоре также может получить ссылку для перелистывания страниц, а затем отправить полученную ссылку для подкачки планировщику, тем самым образуя замкнутый цикл.
Схема развертывания
Схема развертывания- Fetcher развертывается во внешней сети, чтобы предотвратить получение IP-источника антикраулер-системой. Семь Fetchers развернуты онлайн. Fetchers в основном используются для HTTP-доступа, сканирования html-страниц и использования асинхронного ввода-вывода.
- Процессор развернут во внутренней сети, а 2 узла развернуты в сети.
- Планировщик развертывается во внутренней сети, один узел развертывается в сети и развертывается на том же компьютере, что и WebUI.
- ResultWorker развернут во внутренней сети и развернуты 2 узла.
- WebUI развернут во внутренней сети, развернуты только 2 узла, а HTTP-аутентификация используется для ограничения доступа к страницам.
- Fetcher, Processor, Scheduler и ResultWorker взаимодействуют друг с другом через очередь Redis (на основе реализации списка). Scheduler контролирует и отвечает за распределение задач сканера. Сканер URL-адресов — это задача, а объект задачи имеет идентификатор task_id, который реализуется на основе значения md5 URL-адреса по умолчанию, который используется для дедупликации URL-адресов. Каждая задача имеет приоритет по умолчанию, который пользователи могут использовать в методах index_page и detail_page с помощью @priority; настраивая приоритет, мы можем реализовать обход страниц в глубину или в ширину.
Сравнение BBS положительного процесса пиратства
Автоматический вход и ответКраулер для bbs реализован на языке python, а логика после краулинга на html страницу реализована на языке java. Результаты работы сканера pyspider вставляются в базу данных, а запланированная задача Java считывает записи базы данных для последующей обработки.
-
Зарегистрируйте модуль автоматического входа и ответа в реестре службы. Автоматический вход и ответ на основе HtmlUnit или WebDriver + Headless chrome. Служба распознавания кода подтверждения может распознавать китайский, английский, цифры и вопросы викторины. Коды подтверждения считывания не поддерживаются. Для метода скользящего кода подтверждения используется подключаемый модуль Chrome для ручного копирования содержимого файлов cookie. После успешного входа в систему содержимое файла cookie получается для последующих автоматических ответов и т. д. В то же время, чтобы обеспечить ограниченность файла cookie, действительность файла cookie регулярно проверяется, и файл cookie своевременно обновляется.
-
Задача синхронизации Java начинает считывать запись результатов поискового робота pyspider.
-
Вызвать модуль автоматического входа и ответа в соответствии с полем instanceName базы данных. Поле instanceName предназначено для обеспечения того, чтобы разные страницы результатов поискового робота правильно соответствовали их собственным файлам cookie bbs, что удобно для автоматического ответа. После успешного автоматического ответа вставьте отвеченную страницу в RDS, чтобы облегчить последующую логическую обработку.
-
Прочитайте страницы с ответами из RDS и просмотрите узлы, атрибуты и т. д. каждой страницы, чтобы получить ссылку для загрузки. Обход здесь более сложен и требует различных исключений и нарушений. Извлеченные ссылки могут быть ссылками на сетевой диск, ссылками на вложения и т. д. Большинство вложений на форуме - это короткие ссылки на сетевой диск Baidu, и многие из них требуют ввода кода извлечения, мы реализовали автоматизацию этого процесса. Ссылку на сетевой диск Baidu можно извлечь на странице поста и получить соответствующий код извлечения, затем извлечение реального адреса загрузки осуществляется с помощью NodeJs+PhathomJs. Из-за ограничения скорости сетевого диска Baidu мы впоследствии поддержали метод загрузки точки останова для повышения отказоустойчивости.
-
Классифицировать по полученной ссылке для скачивания. Если это ссылка для загрузки сетевого диска Baidu, вызовите соответствующий класс инструментов сетевого диска Baidu, чтобы получить реальный адрес загрузки файла на сетевом диске и поддерживать одиночную и пакетную загрузку; если это прямой реальный адрес загрузки, загрузить его напрямую.
-
По алгоритму сравнения анализировать положительное пиратство.
-
Вставьте сравниваемые результаты в базу данных, а затем выполните статистический анализ.
Извлеките реальный адрес загрузки сетевого диска Baidu.
Извлеките ссылку на сетевой диск-
После того, как временная задача java получает ответное сообщение, она анализирует и обходит узлы и содержимое элемента html и извлекает ссылку на сетевой диск и соответствующий код извлечения в соответствии с извлекаемым. После получения ссылки и кода извлечения PhantomJs выполняет js для получения информации об извлеченной ссылке на сетевой диск, что может включать распознавание кода проверки, поскольку сетевой диск Baidu ограничивает количество раз, когда одна и та же ссылка может извлечь реальный адрес загрузки в короткое время, и он будет извлечен более трех раз.Требуется код подтверждения. После получения всей необходимой информации, такой как: токен, логин и т. д., затем используйте java для отправки ajax-запроса с этими параметрами для получения реального адреса загрузки. Загруженный файл может быть сжатым пакетом или одним файлом apk, в зависимости от того, предоставляете ли вы общий доступ к нескольким файлам или к одному файлу.
-
Сценарий js для извлечения информации о сетевом диске Baidu:
var page = require('webpage').create(), stepIndex = 0, loadInProgress = false;
var fs = require('fs');
var system = require('system');
page.viewportSize = {
width: 480,
height: 800
};
// 命令行参数
var args = system.args;
var codeVal = (args[2] === 'null') ? "" : args[2], loadUrl = args[1];
console.log('codeVal=' + codeVal + '; loadUrl=' + loadUrl);
// 直接运行脚本的参数
// var codeVal = "nd54";
// var loadUrl = "https://pan.baidu.com/s/1sltxlYP";
// phantom.setProxy('116.62.112.142','16816', 'http', 'jingxuan2046', 'p4ke0xy1');
// 配置
page.settings.userAgent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36';
page.settings.resourceTimeout = 5000;
// 回调函数
page.onConsoleMessage = function (msg) {
console.log(msg);
};
page.onResourceRequested = function (request) {
//console.log('Request Faild:' + JSON.stringify(request, undefined, 4));
};
page.onError = function (msg, trace) {
var msgStack = ['PHANTOM ERROR: ' + msg];
if (trace && trace.length) {
msgStack.push('TRACE:');
trace.forEach(function (t) {
msgStack.push(
' -> ' + (t.file || t.sourceURL) + ': ' + t.line + (t.function
? ' (in function ' + t.function + ')' : ''));
});
}
console.error(msgStack.join('\n'));
// phantom.exit(1);
};
// 变量定义
var baiDuObj;
var steps = [
function () {
page.clearCookies();// 每次请求之前先清理cookie
if (loadUrl === null || loadUrl.length == 0) {
console.error('loadUrl不能为空串!');
phantom.exit();
return;
}
// 渲染页面
console.log("加载页面中... loadUrl= " + loadUrl);
// 目的是为了先刷新出必要的Cookie,再去访问shareUrl,不然会报403
page.open(loadUrl, function (status) {
console.log('status=' + status);
setTimeout(function () {
page.evaluate(function (loadUrl) {
// console.log(document.cookie)
window.location.href = loadUrl;
}, loadUrl);
}, 500)
});
},
function () {
// page.render('step1.png');
var currentUrl = page.url;
console.log("currentUrl=" + currentUrl);
console.log("codeVal=" + codeVal);
if (currentUrl === null || currentUrl === "") {
console.log('当前url为空,脚本退出执行...');
phantom.exit(1);
return;
}
// 提取码为空时就不需要再输入了
if (codeVal === null || codeVal.length == 0 || codeVal === "") {
console.log('当前分享不需要提取码...');
return;
}
// 自动输入提取码
page.evaluate(function (codeVal) {
// 当请求页面中不存在accessCode元素时,就不继续执行了
var accessCodeEle = document.getElementsByTagName('input').item(0);
console.log(accessCodeEle);
if (accessCodeEle === null) {
console.info("页面不存在accessCode元素..." + accessCodeEle);
} else {
accessCodeEle.value = codeVal;
var element = document.getElementsByClassName('g-button').item(0);
console.log(element);
var event = document.createEvent("MouseEvents");
event.initMouseEvent(
"click", // 事件类型
true,
true,
window,
1,
0, 0, 0, 0, // 事件的坐标
false, // Ctrl键标识
false, // Alt键标识
false, // Shift键标识
false, // Meta键标识
0, // Mouse左键
element); // 目标元素
element.dispatchEvent(event);
// 点击提交提取码,然后跳转到下载页面
element.click();
}
}, codeVal);
},
function () {
// page.render('step2.png');
page.includeJs('https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js',
function () {
baiDuObj = page.evaluate(function () {
var yunData = window.yunData;
var cookies = document.cookie;
var panAPIUrl = location.protocol + "//" + location.host + "/api/";
var shareListUrl = location.protocol + "//" + location.host
+ "/share/list";
// 变量定义
var sign, timestamp, logid, bdstoken, channel, shareType, clienttype, encrypt, primaryid, uk, product, web, app_id, extra, shareid, is_single_share;
var fileList = [], fidList = [];
var vcode;// 验证码
// 初始化参数
function initParams() {
shareType = getShareType();
sign = yunData.SIGN;
timestamp = yunData.TIMESTAMP;
bdstoken = yunData.MYBDSTOKEN;
channel = 'chunlei';
clienttype = 0;
web = 1;
app_id = 250528;
logid = getLogID();
encrypt = 0;
product = 'share';
primaryid = yunData.SHARE_ID;
uk = yunData.SHARE_UK;
shareid = yunData.SHARE_ID;
is_single_share = isSingleShare();
if (shareType == 'secret') {
extra = getExtra();
}
if (is_single_share) {
var obj = {};
if (yunData.CATEGORY == 2) {
obj.filename = yunData.FILENAME;
obj.path = yunData.PATH;
obj.fs_id = yunData.FS_ID;
obj.isdir = 0;
} else {
obj.filename = yunData.FILEINFO[0].server_filename;
obj.path = yunData.FILEINFO[0].path;
obj.fs_id = yunData.FILEINFO[0].fs_id;
obj.isdir = yunData.FILEINFO[0].isdir;
}
fidList.push(obj.fs_id);
fileList.push(obj);
} else {
fileList = getFileList();
$.each(fileList, function (index, element) {
fidList.push(element.fs_id);
});
}
}
//判断分享类型(public或者secret)
function getShareType() {
return yunData.SHARE_PUBLIC === 1 ? 'public' : 'secret';
}
//判断是单个文件分享还是文件夹或者多文件分享
function isSingleShare() {
return yunData.getContext === undefined;
}
// 获取cookie
function getCookie(e) {
var o, t;
var n = document, c = decodeURI;
return n.cookie.length > 0 && (o = n.cookie.indexOf(e + "="), -1
!= o) ? (o = o + e.length + 1, t = n.cookie.indexOf(";", o), -1
== t && (t = n.cookie.length), c(n.cookie.substring(o, t))) : "";
}
// 私密分享时需要sekey
function getExtra() {
var seKey = decodeURIComponent(getCookie('BDCLND'));
return '{' + '"sekey":"' + seKey + '"' + "}";
}
function base64Encode(t) {
var a, r, e, n, i, s, o = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
for (e = t.length, r = 0, a = ""; e > r;) {
if (n = 255 & t.charCodeAt(r++), r == e) {
a += o.charAt(n >> 2);
a += o.charAt((3 & n) << 4);
a += "==";
break;
}
if (i = t.charCodeAt(r++), r == e) {
a += o.charAt(n >> 2);
a += o.charAt((3 & n) << 4 | (240 & i) >> 4);
a += o.charAt((15 & i) << 2);
a += "=";
break;
}
s = t.charCodeAt(r++);
a += o.charAt(n >> 2);
a += o.charAt((3 & n) << 4 | (240 & i) >> 4);
a += o.charAt((15 & i) << 2 | (192 & s) >> 6);
a += o.charAt(63 & s);
}
return a;
}
// 获取登录id
function getLogID() {
var name = "BAIDUID";
var u = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/~!@#¥%……&";
var d = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;
var f = String.fromCharCode;
function l(e) {
if (e.length < 2) {
var n = e.charCodeAt(0);
return 128 > n ? e : 2048 > n ? f(192 | n >>> 6) + f(
128 | 63 & n) : f(224 | n >>> 12 & 15) + f(
128 | n >>> 6 & 63) + f(128 | 63 & n);
}
var n = 65536 + 1024 * (e.charCodeAt(0) - 55296)
+ (e.charCodeAt(1) - 56320);
return f(240 | n >>> 18 & 7) + f(128 | n >>> 12 & 63) + f(
128 | n >>> 6 & 63) + f(128 | 63 & n);
}
function g(e) {
return (e + "" + Math.random()).replace(d, l);
}
function m(e) {
var n = [0, 2, 1][e.length % 3];
var t = e.charCodeAt(0) << 16 | (e.length > 1 ? e.charCodeAt(1)
: 0) << 8 | (e.length > 2 ? e.charCodeAt(2) : 0);
var o = [u.charAt(t >>> 18), u.charAt(t >>> 12 & 63),
n >= 2 ? "=" : u.charAt(t >>> 6 & 63),
n >= 1 ? "=" : u.charAt(63 & t)];
return o.join("");
}
function h(e) {
return e.replace(/[\s\S]{1,3}/g, m);
}
function p() {
return h(g((new Date()).getTime()));
}
function w(e, n) {
return n ? p(String(e)).replace(/[+\/]/g, function (e) {
return "+" == e ? "-" : "_";
}).replace(/=/g, "") : p(String(e));
}
return w(getCookie(name));
}
//获取当前目录
function getPath() {
var hash = location.hash;
var regx = /(^|&|\/)path=([^&]*)(&|$)/i;
var result = hash.match(regx);
return decodeURIComponent(result[2]);
}
//获取分类显示的类别,即地址栏中的type
function getCategory() {
var hash = location.hash;
var regx = /(^|&|\/)type=([^&]*)(&|$)/i;
var result = hash.match(regx);
return decodeURIComponent(result[2]);
}
function getSearchKey() {
var hash = location.hash;
var regx = /(^|&|\/)key=([^&]*)(&|$)/i;
var result = hash.match(regx);
return decodeURIComponent(result[2]);
}
//获取当前页面(list或者category)
function getCurrentPage() {
var hash = location.hash;
return decodeURIComponent(
hash.substring(hash.indexOf('#') + 1, hash.indexOf('/')));
}
//获取文件信息列表
function getFileList() {
var result = [];
if (getPath() == '/') {
result = yunData.FILEINFO;
} else {
logid = getLogID();
var params = {
uk: uk,
shareid: shareid,
order: 'other',
desc: 1,
showempty: 0,
web: web,
dir: getPath(),
t: Math.random(),
bdstoken: bdstoken,
channel: channel,
clienttype: clienttype,
app_id: app_id,
logid: logid
};
$.ajax({
url: shareListUrl,
method: 'GET',
async: false,
data: params,
success: function (response) {
if (response.errno === 0) {
result = response.list;
}
}
});
}
return result;
}
//生成下载时的fid_list参数
function getFidList(list) {
var retList = null;
if (list.length === 0) {
return null;
}
var fileidlist = [];
$.each(list, function (index, element) {
fileidlist.push(element.fs_id);
});
retList = '[' + fileidlist + ']';
return retList;
}
// 初始化
initParams();
// console.log('fileList=---------' + fileList);
// console.log('fidList=---------' + getFidList(fileList))
var retObj = {
'sign': sign,
'timestamp': timestamp,
'logid': logid,
'bdstoken': bdstoken,
'channel': channel,
'shareType': shareType,
'clienttype': clienttype,
'encrypt': encrypt,
'primaryid': primaryid,
'uk': uk,
'product': product,
'web': web,
'app_id': app_id,
'extra': extra,
'shareid': shareid,
'fid_list': getFidList(fileList),// 要下载的文件id
'file_list': fileList,
'panAPIUrl': panAPIUrl,
'single_share': is_single_share,
'cookies': cookies
};
return retObj;
});
console.log("data=" + JSON.stringify(baiDuObj));
});
},
function () {
}
];
// main started
setInterval(function () {
if (!loadInProgress && typeof steps[stepIndex] == "function") {
console.log(
' ');
console.log(
'===============================================================================================');
console.log(' step ' + (stepIndex + 1)
+ ' ');
console.log(
'===============================================================================================');
console.log(
' ');
steps[stepIndex]();
stepIndex++;
}
if (typeof steps[stepIndex] != "function") {
console.log("Completed!");
console.log('FinalOutPut: codeVal=' + codeVal + "; loadUrl=" + loadUrl
+ "; result=" + JSON.stringify(baiDuObj));
phantom.exit();
}
}, 5000);
Выше приведен примерно общий процесс обхода всего bbs. Из-за ограниченного места некоторые места могут быть расплывчатыми или неясными, добро пожаловать на обсуждение. Мой технический уровень ограничен, и я приветствую критику и исправления, если есть ошибки или недостатки.