Работает обработка ссылок на таблицы с численностью

This commit is contained in:
2024-09-06 14:11:38 +03:00
parent 04374fef40
commit 2be45826c1
1698 changed files with 138656 additions and 174 deletions

View File

@ -8,21 +8,7 @@ enum Color : string
case RED = "\033[91m";
case BLUE = "\033[94m";
public static function create(string $color) : Color
{
switch ($color) {
case 'green':
return self::GREEN;
case 'red':
return self::RED;
case 'blue':
return self::BLUE;
default:
return self::WHITE;
}
}
public function tostring() : string
public function tostring(): string
{
return $this->value;
}

View File

@ -9,6 +9,8 @@ use PDO;
final class Database
{
private PDO $pdo;
public const FILE_ADD_RECORDING ='not-recorded-in-db.yaml';
private const ERR_NO_CONNECT = "HY000";
private static $logfile = 'log/database.log';
private DatabaseConfig $databaseConfig;
private DatabaseLogger $logger;
@ -48,13 +50,11 @@ final class Database
}
/**
* Выборка данных из базы
* @param string $sql
* SQL-запрос
* @param array $params
* Параметры запроса
* @param string $sql SQL-запрос
* @param array $params Параметры запроса
* @return array
*/
public function select(string $sql, array $params = []) : array
public function select(string $sql, array $params = []): array
{
try {
$stmt = $this->pdo->prepare($sql);
@ -74,13 +74,11 @@ final class Database
}
/**
* Добавление данных в базу
* @param string $sql
* SQL-запрос
* @param array $params
* Параметры запроса
* @param string $sql SQL-запрос
* @param array $params Параметры запроса
* @return void
*/
public function insert(string $sql, array $params)
public function insert(string $sql, array $params): void
{
try {
$stmt = $this->pdo->prepare($sql);
@ -100,21 +98,23 @@ final class Database
$message = "Ошибка запроса:" . $e->getMessage();
$this->logger->log($message);
// При ошибке запроса сохраняем валидные данные в yaml-файл
if ($e->getCode() === "HY000") {
if ($e->getCode() === self::ERR_NO_CONNECT) {
$yaml = Yaml::dump($params);
file_put_contents('not-recorded-in-db.yaml', $yaml, FILE_APPEND);
file_put_contents(
self::FILE_ADD_RECORDING,
$yaml,
FILE_APPEND
);
}
}
}
/**
* Обновление данных в базе
* @param string $sql
* SQL-запрос
* @param array $params
* Параметры запроса
* @param string $sql SQL-запрос
* @param array $params Параметры запроса
* @return void
*/
public function update(string $sql, array $params)
public function update(string $sql, array $params): void
{
try {
$stmt = $this->pdo->prepare($sql);

View File

@ -24,7 +24,7 @@ final class DatabaseConfig
$this->_password = $config['DB_PASSWORD'];
}
private function getDataEnv(string $db) : array
private function getDataEnv(string $db): array
{
$envVars = parse_ini_file('.env', true);
$db = strtoupper($db);
@ -42,7 +42,7 @@ final class DatabaseConfig
return $this->_dbname;
}
public function getDsn() : string
public function getDsn(): string
{
return $this->_driver.":host=".$this->_host
.";dbname=".$this->_dbname
@ -50,12 +50,12 @@ final class DatabaseConfig
.";port=".$this->_port;
}
public function getUsername() : string
public function getUsername(): string
{
return $this->_username;
}
public function getPassword() : string
public function getPassword(): string
{
return $this->_password;
}

View File

@ -3,11 +3,12 @@ namespace ContingentParser\Database;
use NilPortugues\Sql\QueryBuilder\Builder\GenericBuilder;
class DatabaseFacade
final class DatabaseFacade
{
private GenericBuilder $builder;
private Database $opendata;
private Database $niimko;
public const FILE_ADD_RECORDING = Database::FILE_ADD_RECORDING;
private array $specialties;
private array $universities;
/**
@ -24,7 +25,7 @@ class DatabaseFacade
* Извлечение URL сайтов из базы данных niimko
* @return array
*/
public function getSitesFromNiimko() : array
public function getSitesFromNiimko(): array
{
/*
SELECT kod AS org_id, site FROM niimko.s_vuzes
@ -53,7 +54,7 @@ class DatabaseFacade
* Сайты, у которых устаревшие URL
* @return array
*/
public function getSitesFromMiccedu(array $params) : array
public function getSitesFromMiccedu(array $params): array
{
/*
SELECT site, vuzkod AS org_id FROM opendata.miccedu_monitoring
@ -86,7 +87,7 @@ class DatabaseFacade
* Массив записей численности по специальностям
* @return void
*/
public function insertContingent(array $contingent) : void
public function insertContingent(array $contingent): void
{
/*
INSERT INTO sveden_education_contingent
@ -116,7 +117,7 @@ class DatabaseFacade
* Публичное получение специальностей
* @return array
*/
public function specialties() : array
public function specialties(): array
{
return $this->specialties ? $this->specialties : [];
}
@ -124,7 +125,7 @@ class DatabaseFacade
* Публичное получение id вузов, занесенных в базу opendata
* @return array
*/
public function universities() : array
public function universities(): array
{
return $this->universities ? $this->universities : [];
}
@ -132,7 +133,7 @@ class DatabaseFacade
* Извлечение кодов специальности из базы данных niimko
* @return array
*/
private function getSpecialties() : array
private function getSpecialties(): array
{
/*
SELECT id AS spec_id, kod AS spec_code FROM niimko.s_specs
@ -155,7 +156,7 @@ class DatabaseFacade
* Извлечение id вузов, занесенных в базу opendata
* @return array
*/
private function getUniversities() : array
private function getUniversities(): array
{
/*
SELECT DISTINCT org_id FROM sveden_education_contingent
@ -180,7 +181,7 @@ class DatabaseFacade
* Массив [['org_id' => val1, 'site' => val1,],...]
* @return void
*/
public function updateSitesOpendata(array $params) : void
public function updateSitesOpendata(array $params): void
{
/*
UPDATE niimko.s_vuzes

View File

@ -7,7 +7,7 @@ use ContingentParser\Http\UrlBuilder;
use ContingentParser\Logger\HtmlLogger;
use ContingentParser\Parser\ContingentFacade;
class Facade
final class Facade
{
private DatabaseFacade $databaseFacade;
private HttpClientFacade $httpClientFacade;
@ -27,13 +27,12 @@ class Facade
}
/**
* Получить массив сайтов
* @param array $params
* Массив сайтов, у которых нужны обновиленные URL
* @param array $params Массив сайтов, у которых нужны обновиленные URL
* @return array
*/
public function getSites(array $params = []) : array
public function getSites(array $params = []): array
{
if (empty($params)) {
if (!$params) {
return $this->databaseFacade->getSitesFromNiimko();
} else {
return $this->databaseFacade->getSitesFromMiccedu($params);
@ -42,65 +41,72 @@ class Facade
/**
* Cобирает из микроразметки данные таблицы
* "Информация о численности обучающихся" в разделе "Образование"
* @param array $site
* Сайт содержащий id организации и URL
* @param array $site Сайт содержащий id организации и URL
* @return void
*/
public function collectDataFromContingent(array $site) : void
public function collectDataFromContingent(array $site): void
{
list('org_id' => $orgId, 'site' => $url) = $site;
// Нет URL сайта вуза
if (empty($site)) {
// $httpLogger->log($orgId);
return;
}
// Уже в базе
if (in_array($orgId, $this->databaseFacade->universities())) {
if ($this->isExit($site)) {
return;
}
$url = $this->urlBuilder->build($url);
Printer::println(implode(' ', $site), 'green');
$html = $this->httpClientFacade->processEducationContingentSites(
Printer::println(implode(' ', $site), Color::GREEN);
$html = $this->httpClientFacade->getContentOfSite(
$url,
$site
);
$uri = $this->contingentFacade->getLink($html);
if ($uri) {
$pattern = '/^https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&\/=]*)$/';
if (preg_match($pattern, $uri)) {
$html = $this->httpClientFacade->getContentOfSite(
$url,
$site
);
} else if ($this->urlBuilder->checkUri($uri)) {
$html = $this->httpClientFacade->getContentOfSite(
$url,
$site,
$uri
);
}
}
// Получаем данные таблицы численности
$contingent = $this->contingentFacade->getContingent(
$html,
$this->databaseFacade->specialties(),
$orgId
$site['org_id']
);
if ($contingent) {
// $contingent = $this->contingentFacade->getContingentFromLink($html);
// if ($contingent) {
Printer::println("No result", 'red');
$this->htmlLogger->log("$orgId $url");
// }
if ($this->contingentFacade->isValidContingent($contingent)
&& $contingent
) {
// Заносим в базу
Printer::print_r($contingent, Color::BLUE);
$this->databaseFacade->insertContingent($contingent);
} else {
if ($this->contingentFacade->isValidContingent($contingent)) {
// Заносим в базу
Printer::print_r($contingent, 'blue');
$this->databaseFacade->insertContingent($contingent);
} else {
$this->htmlLogger->log("$orgId $url");
Printer::println("No result", 'red');
}
Printer::println("No result", Color::RED);
$this->htmlLogger->log("$orgId $url");
}
Printer::println();
}
public function getExclusionSites(string $path) : array
/**
* Условие выхода
* @param array $site
* @return bool
*/
private function isExit(array $site): bool
{
$logs = file($path);
$result = [];
foreach ($logs as $log) {
$data = explode(' ', $log);
$result[] = [
'org_id' => $data[2],
'site' => $data[3] ? $data[3] : ''
];
// Нет URL сайта вуза
if (!$site['site']) {
return true;
}
return $result;
// Уже в базе
if (in_array($site['org_id'], $this->databaseFacade->universities())) {
return true;
}
return false;
}
}

View File

@ -1,6 +1,7 @@
<?php
namespace ContingentParser\Http;
use ContingentParser\Color;
use ContingentParser\Logger\HttpLogger;
use ContingentParser\Printer;
use CurlHandle;
@ -12,16 +13,17 @@ final class CurlHelper
private CurlHandle|bool $curl;
private string $url;
private array $site;
private int $countRedirect;
private const MAX_REDIRECT = 5;
/**
* Коснтруктор
* Инициализация сессии
* @param string $url
* URL сайта
* @param array $site
* Идентификатор организации и базовый URL сайта
* @param string $url URL сайта
* @param array $site Идентификатор организации и базовый URL сайта
*/
public function __construct(string $url, array $site)
{
$this->countRedirect = 0;
$this->url = $url;
$this->site = $site;
@ -49,21 +51,25 @@ final class CurlHelper
* Получить html-разметку
* @return string
*/
public function getContent() : string
public function getContent(): string
{
curl_setopt($this->curl, CURLOPT_URL, $this->url);
$html = curl_exec($this->curl);
if ($this->checkLocation($this->url, $html)) {
$html = $this->getContent();
if ($this->countRedirect < self::MAX_REDIRECT) {
curl_setopt($this->curl, CURLOPT_URL, $this->url);
$html = curl_exec($this->curl);
if ($this->checkLocation($this->url, $html)) {
$this->countRedirect++;
$html = $this->getContent();
}
return $html;
}
return $html;
return '';
}
/**
* Summary of checkLocation
* @param string $html
* @return bool
*/
private function checkLocation(string &$url, string $html) : bool
private function checkLocation(string &$url, string $html): bool
{
preg_match('/location:(.*?)\n/i', $html, $matches);
if (empty($matches)) return false;
@ -77,14 +83,14 @@ final class CurlHelper
* Сообщить об ошибке
* @return void
*/
public function reportError() : void
public function reportError(): void
{
$httpLogger = new HttpLogger('log/http-curl.log');
$httpCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
if ($httpCode != 200 && $httpCode != 0) {
Printer::println("HTTP-code: $httpCode", 'red');
Printer::println("HTTP-code: $httpCode", Color::RED);
$message = implode(' ', $this->site) . ' HTTP-code(' . $httpCode.')';
$httpLogger->log($message, $httpCode);
} else if ($httpCode == 0) {
@ -93,7 +99,7 @@ final class CurlHelper
$message .= " cURL error ({$errno}): ".curl_strerror($errno);
$httpLogger->log($message);
} else {
Printer::println("HTTP-code: $httpCode", 'blue');
Printer::println("HTTP-code: $httpCode", Color::BLUE);
}
}
}

View File

@ -1,6 +1,7 @@
<?php
namespace ContingentParser\Http;
use ContingentParser\Color;
use ContingentParser\Printer;
use GuzzleHttp\Client;
use GuzzleHttp\TransferStats;
@ -17,10 +18,11 @@ final class HttpClientFacade
* @param array $site Идентификатор организации, и базовый URL
* @return string
*/
public function processEducationContingentSites(
public function getContentOfSite(
string $url,
array $site
) : string {
array $site,
string $uri = "sveden/education/"
): string {
try {
$client = $this->createClient($url);
// Запрос по базовому uri
@ -30,18 +32,18 @@ final class HttpClientFacade
}
]);
Printer::println("Redirect $url -> $redirectUrl");
$url .= substr($url, -1) == '/' ? '':'/';
$url .= "sveden/education/study";
$url .= substr($url, -1) == '/' ? '' : '/';
$url .= substr($uri, 0, 1) == '/' ? substr($uri, 1) : $uri;
Printer::println("Parsing for $url");
$response = $client->get($url);
$httpCode = $response->getStatusCode();
Printer::println("HTTP-code: $httpCode", 'blue');
Printer::println("HTTP-code: $httpCode", Color::BLUE);
$html = $response->getBody()->getContents();
} catch (\Exception $e
) {
Printer::println("HTTP-code: ".$e->getCode(), 'red');
Printer::println("HTTP-code: ".$e->getCode(), Color::RED);
$html = $this->handleException($url, $site);
} finally {
return $html;
@ -75,7 +77,7 @@ final class HttpClientFacade
* Конфигурация клиента
* @return array
*/
private function config() : array
private function config(): array
{
return [
'force_ip_resolve' => 'v4',
@ -87,7 +89,7 @@ final class HttpClientFacade
'protocols' => ['http', 'https'],
'track_redirects' => true
],
'connect_timeout' => 300.0,
'connect_timeout' => 90.0,
'verify' => false,
'headers' => [
'User-Agent' => 'Mozilla/5.0 (X11; Linux x86_64) '

View File

@ -1,16 +1,15 @@
<?php
namespace ContingentParser\Http;
class UrlBuilder
final class UrlBuilder
{
public function __construct() {}
/**
* Строит валидный URL сайта
* @param string $url
* Изначальный URL
* @param string $url Изначальный URL
* @return string
*/
public function build(string $url) : string
public function build(string $url): string
{
// Строит -> https://<base_uri>
$url = trim(strtolower($url));
@ -18,13 +17,26 @@ class UrlBuilder
$url = str_replace("www/", "www.", $url);
$url = str_replace("http:\\\\", "", $url);
if (!preg_match('#^https?://#', $url)) {
$url = "http://$url";
$url = "https://$url";
}
// $url = str_replace("http://", "https://", $url);
$url = str_replace("http://", "https://", $url);
$arr = parse_url($url);
$url = $arr['scheme'] . '://' . $arr['host'] . '/';
// $url = str_replace("www.", "", $url);
$url = str_replace("www.", "", $url);
$url = str_replace("_", "/", $url);
return trim($url);
}
public function checkUri(string $uri): bool
{
if (str_ends_with($uri, ".pdf")
|| str_ends_with($uri, ".docx")
|| str_ends_with($uri, ".doc")
|| str_starts_with($uri, "javascript")
) {
return false;
}
return true;
}
}

View File

@ -3,7 +3,7 @@ namespace ContingentParser\Logger;
final class DatabaseLogger extends Logger
{
public function log(string $message) : void
public function log(string $message): void
{
$date = date('Y-m-d H:i:s');
$logMessage = "[$date] $message\n";

View File

@ -1,9 +1,9 @@
<?php
namespace ContingentParser\Logger;
class HtmlLogger extends Logger
final class HtmlLogger extends Logger
{
public function log(string $message) : void
public function log(string $message): void
{
$date = date('Y-m-d H:i:s');
$logMessage = "[$date] $message\n";

View File

@ -55,7 +55,7 @@ final class HttpLogger extends Logger
510 => 'Not Extended',
511 => 'Network Authentication Required'
);
public function log(string $message, int $httpCode = null) : void
public function log(string $message, int $httpCode = null): void
{
$date = date('Y-m-d H:i:s');
if (empty($httpCode)) {

View File

@ -1,23 +1,20 @@
<?php
namespace ContingentParser\Parser;
class ContingentFacade
final class ContingentFacade
{
/**
* Получить данные о численности
* @param string $html
* Разметка сайта вуза
* @param mixed $specialties
* Массив специальностей
* @param int $orgId
* Идентификатор организации
* @param string $html Разметка сайта вуза
* @param mixed $specialties Массив специальностей
* @param int $orgId Идентификатор организации
* @return array
*/
public function getContingent(
string $html,
array $specialties,
int $orgId
) : array {
): array {
$parser = new ContingentParser($html);
$contingent = $parser->getDataTable();
$this->addSpecId($contingent, $specialties);
@ -27,11 +24,10 @@ class ContingentFacade
}
/**
* Проверка на валидность записи численнести
* @param array $contingent
* Массив численности по специальностям
* @param array $contingent Массив численности по специальностям
* @return bool
*/
public function isValidContingent(array $contingent) : bool
public function isValidContingent(array $contingent): bool
{
$count = 0;
foreach ($contingent as $value) {
@ -41,13 +37,11 @@ class ContingentFacade
}
/**
* Добавить идентификатор специальности в запись численности
* @param array $contingent
* Массив численности по специальностям
* @param array $specialties
* Массив специальностей
* @param array $contingent Массив численности по специальностям
* @param array $specialties Массив специальностей
* @return void
*/
private function addSpecId(array &$contingent, array $specialties) : void
private function addSpecId(array &$contingent, array $specialties): void
{
$specIdMap = array_column($specialties, 'spec_id', 'spec_code');
foreach ($contingent as $key => $con) {
@ -56,10 +50,8 @@ class ContingentFacade
}
/**
* Добавить идентификатор организации в запись численности
* @param array $contingent
* Массив численности по специальностям
* @param int $orgId
* Идентифиактор организации
* @param array $contingent Массив численности по специальностям
* @param int $orgId Идентифиактор организации
* @return void
*/
private function addOrgId(array &$contingent, int $orgId): void
@ -69,13 +61,13 @@ class ContingentFacade
}
}
/**
* Summary of getContingentFromLink
*
* @param string $html
* @return array
* @return string
*/
// public function getContingentFromLink(string $html): array
// {
// $parser = new ContingentParser($html);
// $
// }
public function getLink(string $html): string
{
$parser = new ContingentParser($html);
return $parser->getLink();
}
}

View File

@ -1,10 +1,14 @@
<?php
/**
* Парсер информации об образовательной организации
* с её сайта с использованием микроразметки
*/
namespace ContingentParser\Parser;
use DOMDocument;
use DOMXPath;
class ContingentParser
final class ContingentParser
{
private ?DOMXPath $xpath;
private DOMDocument $dom;
@ -33,7 +37,7 @@ class ContingentParser
}
}
private function setEncoding(string &$html) : void
private function setEncoding(string &$html): void
{
$encoding = mb_detect_encoding($html, 'UTF-8, windows-1251');
if ($encoding != self::ENCODING) {
@ -46,7 +50,7 @@ class ContingentParser
}
$html = mb_convert_encoding($html,'HTML-ENTITIES','UTF-8');
}
public function getDataTable() : array
public function getDataTable(): array
{
if (empty($this->xpath)) return [];
@ -77,7 +81,7 @@ class ContingentParser
return $records;
}
private function parseContingent() : array
private function parseContingent(): array
{
$data = [];
foreach (self::FIELDS as $field => $tag) {
@ -100,8 +104,15 @@ class ContingentParser
public function getLink(): string
{
$needle = "Информация о численности обучающихся";
$data = $this->dom->getElementsByTagName('a');
var_dump($data->item(0)->getAttribute('href'));
for ($i = 0; $i < $data->length; $i++) {
$haystack = $data->item($i)->textContent;
$isInformationOfContingent = strpos($haystack, $needle) !== false;
if ($isInformationOfContingent) {
return $data->item($i)->getAttribute('href');
}
}
return '';
}
}

View File

@ -1,7 +1,7 @@
<?php
namespace ContingentParser\Parser;
class ContingentRow
final class ContingentRow
{
public function __construct(
private string $eduCode,
@ -20,7 +20,7 @@ class ContingentRow
$this->contingent = $contingent;
}
public function getData() : array
public function getData(): array
{
return [
"spec_code" => $this->eduCode,

View File

@ -1,26 +1,29 @@
<?php
namespace ContingentParser;
class Printer
final class Printer
{
public static function print(string $text = '', string $color = '') : void
{
$color = Color::create($color);
public static function print(
int|string $text = '',
Color $color = Color::WHITE
): void {
print($color->tostring().$text.Color::WHITE->tostring());
}
public static function println(string $text = '', string $color = '') : void
{
$color = Color::create($color);
public static function println(
int|string $text = '',
Color $color = Color::WHITE
): void {
print($color->tostring().$text.Color::WHITE->tostring());
print(PHP_EOL);
}
public static function print_r(mixed $value, string $color = '') : void
{
$color = Color::create($color);
public static function print_r(
mixed $value,
Color $color = Color::WHITE
): void {
print($color->tostring());
print_r($value);
print(Color::WHITE->tostring());
}
}
}