mirror of
https://github.com/YGGverse/Yo.git
synced 2025-01-13 16:27:54 +00:00
initial commit
This commit is contained in:
parent
a5fabf2381
commit
7dfc800a67
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/vendor/
|
||||
composer.lock
|
41
README.md
41
README.md
@ -1,2 +1,39 @@
|
||||
# Yo
|
||||
Tiny web crawler in PHP & Manticore
|
||||
# Yo!
|
||||
|
||||
Micro Web Crawler in PHP & Manticore
|
||||
|
||||
## CLI
|
||||
|
||||
### Index
|
||||
|
||||
#### Init
|
||||
|
||||
Create initial index
|
||||
|
||||
```
|
||||
php src/cli/index/init.php [reset]
|
||||
```
|
||||
* `reset` - optional, reset existing index
|
||||
|
||||
### Document
|
||||
|
||||
#### Add
|
||||
|
||||
```
|
||||
php src/cli/document/add.php URL
|
||||
```
|
||||
* `URL` - add new URL to the crawl queue
|
||||
|
||||
#### Crawl
|
||||
|
||||
```
|
||||
php src/cli/document/crawl.php
|
||||
```
|
||||
|
||||
#### Search
|
||||
|
||||
```
|
||||
php src/cli/document/search.php '@title "*"' [limit]
|
||||
```
|
||||
* `query` - required
|
||||
* `limit` - optional search results limit
|
21
composer.json
Normal file
21
composer.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "yggverse/yo",
|
||||
"description": "Yo! Micro Web Crawler in PHP & Manticore",
|
||||
"type": "project",
|
||||
"license": "MIT",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Yggverse\\Yo\\": "src/"
|
||||
}
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "YGGverse"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"manticoresoftware/manticoresearch-php": "^3.1",
|
||||
"symfony/css-selector": "^6.3",
|
||||
"symfony/dom-crawler": "^6.3"
|
||||
}
|
||||
}
|
57
src/cli/document/add.php
Normal file
57
src/cli/document/add.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
// Load dependencies
|
||||
require_once __DIR__ . '/../../../vendor/autoload.php';
|
||||
|
||||
// Init config
|
||||
$config = json_decode(
|
||||
file_get_contents(
|
||||
__DIR__ . '/../../config.json'
|
||||
)
|
||||
);
|
||||
|
||||
// Init
|
||||
$client = new \Manticoresearch\Client(
|
||||
[
|
||||
'host' => $config->manticore->server->host,
|
||||
'port' => $config->manticore->server->port,
|
||||
]
|
||||
);
|
||||
|
||||
// Init index
|
||||
$index = $client->index(
|
||||
$config->manticore->index->document
|
||||
);
|
||||
|
||||
// Check URL for exist
|
||||
$result = $index->search('@url "' . $argv[1] . '"')
|
||||
->limit(1)
|
||||
->get();
|
||||
|
||||
if ($result->getTotal())
|
||||
{
|
||||
echo sprintf(
|
||||
'URL "%s" already exists in "%s" index!' . PHP_EOL,
|
||||
$argv[1],
|
||||
$config->manticore->index->document
|
||||
);
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
// Add
|
||||
$result = $index->addDocument(
|
||||
[
|
||||
'url' => $argv[1]
|
||||
]
|
||||
);
|
||||
|
||||
echo sprintf(
|
||||
'URL "%s" added to "%s" index: %s' . PHP_EOL,
|
||||
$argv[1],
|
||||
$config->manticore->index->document,
|
||||
print_r(
|
||||
$result,
|
||||
true
|
||||
)
|
||||
);
|
269
src/cli/document/crawl.php
Normal file
269
src/cli/document/crawl.php
Normal file
@ -0,0 +1,269 @@
|
||||
<?php
|
||||
|
||||
// Prevent multi-thread execution
|
||||
$semaphore = sem_get(crc32('yo.cli.document.crawl'), 1);
|
||||
|
||||
if (false === sem_acquire($semaphore, true))
|
||||
{
|
||||
exit ('process execution locked by another thread!' . PHP_EOL);
|
||||
}
|
||||
|
||||
// Load dependencies
|
||||
require_once __DIR__ . '/../../../vendor/autoload.php';
|
||||
|
||||
// Init config
|
||||
$config = json_decode(
|
||||
file_get_contents(
|
||||
__DIR__ . '/../../config.json'
|
||||
)
|
||||
);
|
||||
|
||||
// Init client
|
||||
$client = new \Manticoresearch\Client(
|
||||
[
|
||||
'host' => $config->manticore->server->host,
|
||||
'port' => $config->manticore->server->port,
|
||||
]
|
||||
);
|
||||
|
||||
// Init search
|
||||
$search = new \Manticoresearch\Search(
|
||||
$client
|
||||
);
|
||||
|
||||
$search->setIndex(
|
||||
$config->manticore->index->document
|
||||
);
|
||||
|
||||
$search->match(
|
||||
'*',
|
||||
'url'
|
||||
);
|
||||
|
||||
$search->sort(
|
||||
'time',
|
||||
'asc'
|
||||
);
|
||||
|
||||
$search->limit(
|
||||
$config->cli->document->crawl->queue->limit
|
||||
);
|
||||
|
||||
// Init index
|
||||
$index = $client->index(
|
||||
$config->manticore->index->document
|
||||
);
|
||||
|
||||
// Begin queue
|
||||
foreach($search->get() as $document)
|
||||
{
|
||||
// Update index time
|
||||
$index->updateDocument(
|
||||
[
|
||||
'time' => time()
|
||||
],
|
||||
$document->getId()
|
||||
);
|
||||
|
||||
// Request remote URL
|
||||
$request = curl_init(
|
||||
$document->get('url')
|
||||
);
|
||||
|
||||
curl_setopt(
|
||||
$request,
|
||||
CURLOPT_RETURNTRANSFER,
|
||||
true
|
||||
);
|
||||
|
||||
if ($response = curl_exec($request))
|
||||
{
|
||||
// Update HTTP code
|
||||
if ($code = curl_getinfo($request, CURLINFO_HTTP_CODE))
|
||||
{
|
||||
$index->updateDocument(
|
||||
[
|
||||
'code' => $code
|
||||
],
|
||||
$document->getId()
|
||||
);
|
||||
|
||||
} else continue;
|
||||
|
||||
// Update size
|
||||
if ($size = curl_getinfo($request, CURLINFO_SIZE_DOWNLOAD))
|
||||
{
|
||||
$index->updateDocument(
|
||||
[
|
||||
'size' => $size
|
||||
],
|
||||
$document->getId()
|
||||
);
|
||||
|
||||
} else continue;
|
||||
|
||||
// Update MIME type
|
||||
if ($mime = curl_getinfo($request, CURLINFO_CONTENT_TYPE))
|
||||
{
|
||||
$index->updateDocument(
|
||||
[
|
||||
'mime' => $mime
|
||||
],
|
||||
$document->getId()
|
||||
);
|
||||
|
||||
} else continue;
|
||||
|
||||
// DOM crawler
|
||||
if (false !== stripos($mime, 'text/html'))
|
||||
{
|
||||
$crawler = new Symfony\Component\DomCrawler\Crawler();
|
||||
$crawler->addHtmlContent(
|
||||
$response
|
||||
);
|
||||
|
||||
// Get title
|
||||
$title = '';
|
||||
foreach ($crawler->filter('head > title')->each(function($node) {
|
||||
|
||||
return $node->text();
|
||||
|
||||
}) as $value) {
|
||||
|
||||
$title = html_entity_decode(
|
||||
$value
|
||||
);
|
||||
}
|
||||
|
||||
// Get description
|
||||
$description = '';
|
||||
foreach ($crawler->filter('head > meta[name="description"]')->each(function($node) {
|
||||
|
||||
return $node->attr('content');
|
||||
|
||||
}) as $value) {
|
||||
|
||||
$description = html_entity_decode(
|
||||
$value
|
||||
);
|
||||
}
|
||||
|
||||
// Get keywords
|
||||
$keywords = '';
|
||||
foreach ($crawler->filter('head > meta[name="keywords"]')->each(function($node) {
|
||||
|
||||
return $node->attr('content');
|
||||
|
||||
}) as $value) {
|
||||
|
||||
$keywords = html_entity_decode(
|
||||
$value
|
||||
);
|
||||
}
|
||||
|
||||
// Replace document
|
||||
// https://github.com/manticoresoftware/manticoresearch-php/issues/10#issuecomment-612685916
|
||||
$index->replaceDocument(
|
||||
[
|
||||
'url' => $document->get('url'),
|
||||
'title' => $title,
|
||||
'description' => $description,
|
||||
'keywords' => $keywords,
|
||||
'code' => $code,
|
||||
'size' => $size,
|
||||
'mime' => $mime,
|
||||
'time' => time(),
|
||||
],
|
||||
$document->getId()
|
||||
);
|
||||
|
||||
// Crawl documents
|
||||
$documents = [];
|
||||
|
||||
$scheme = parse_url($document->get('url'), PHP_URL_SCHEME);
|
||||
$host = parse_url($document->get('url'), PHP_URL_HOST);
|
||||
$port = parse_url($document->get('url'), PHP_URL_PORT);
|
||||
|
||||
foreach ($config->cli->document->crawl->selector as $selector => $settings)
|
||||
{
|
||||
foreach ($crawler->filter($selector)->each(function($node) {
|
||||
|
||||
return $node;
|
||||
|
||||
}) as $value) {
|
||||
|
||||
if ($url = $value->attr($settings->attribute))
|
||||
{
|
||||
//Make relative links absolute
|
||||
if (!parse_url($url, PHP_URL_HOST))
|
||||
{
|
||||
$url = $scheme . '://' . $host . ($port ? ':' . $port : null) .
|
||||
'/' .
|
||||
trim(
|
||||
ltrim(
|
||||
str_replace(
|
||||
[
|
||||
'./',
|
||||
'../'
|
||||
],
|
||||
'',
|
||||
$url
|
||||
),
|
||||
'/'
|
||||
),
|
||||
'.'
|
||||
);
|
||||
}
|
||||
|
||||
// Regex rules
|
||||
if (!preg_match($settings->regex, $url))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// External host rules
|
||||
if (!$settings->external && parse_url($url, PHP_URL_HOST) != $host)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$documents[] = $url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($documents)
|
||||
{
|
||||
foreach (array_unique($documents) as $url)
|
||||
{
|
||||
if (!$index->search('@url "' . $url . '"')
|
||||
->limit(1)
|
||||
->get()
|
||||
->getTotal())
|
||||
{
|
||||
$index->addDocument(
|
||||
[
|
||||
'url' => $url
|
||||
]
|
||||
);
|
||||
|
||||
echo sprintf(
|
||||
'add "%s" to "%s"' . PHP_EOL,
|
||||
$url,
|
||||
$config->manticore->index->document
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo sprintf(
|
||||
'index "%s" updated: %s' . PHP_EOL,
|
||||
$config->manticore->index->document,
|
||||
print_r(
|
||||
$document,
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
34
src/cli/document/search.php
Normal file
34
src/cli/document/search.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
// Load dependencies
|
||||
require_once __DIR__ . '/../../../vendor/autoload.php';
|
||||
|
||||
// Init config
|
||||
$config = json_decode(
|
||||
file_get_contents(
|
||||
__DIR__ . '/../../config.json'
|
||||
)
|
||||
);
|
||||
|
||||
// Init client
|
||||
$client = new \Manticoresearch\Client(
|
||||
[
|
||||
'host' => $config->manticore->server->host,
|
||||
'port' => $config->manticore->server->port,
|
||||
]
|
||||
);
|
||||
|
||||
// Init index
|
||||
$index = $client->index(
|
||||
$config->manticore->index->document
|
||||
);
|
||||
|
||||
// Search
|
||||
foreach($index->search($argv[1])
|
||||
->limit($argv[2] ? $argv[2] : 10)
|
||||
->get() as $result)
|
||||
{
|
||||
var_dump(
|
||||
$result
|
||||
);
|
||||
}
|
93
src/cli/index/init.php
Normal file
93
src/cli/index/init.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
// Load dependencies
|
||||
require_once __DIR__ . '/../../../vendor/autoload.php';
|
||||
|
||||
// Init config
|
||||
$config = json_decode(
|
||||
file_get_contents(
|
||||
__DIR__ . '/../../config.json'
|
||||
)
|
||||
);
|
||||
|
||||
// Init client
|
||||
$client = new \Manticoresearch\Client(
|
||||
[
|
||||
'host' => $config->manticore->server->host,
|
||||
'port' => $config->manticore->server->port,
|
||||
]
|
||||
);
|
||||
|
||||
// Init index
|
||||
$index = $client->index(
|
||||
$config->manticore->index->document
|
||||
);
|
||||
|
||||
// Request options
|
||||
if (isset($argv[1]))
|
||||
{
|
||||
switch ($argv[1])
|
||||
{
|
||||
case 'reset':
|
||||
|
||||
$result = $index->drop(true);
|
||||
|
||||
echo sprintf(
|
||||
'index "%s" deleted: %s' . PHP_EOL,
|
||||
$config->manticore->index->document,
|
||||
print_r(
|
||||
$result,
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Init index
|
||||
$result = $index->create(
|
||||
[
|
||||
'url' =>
|
||||
[
|
||||
'type' => 'text'
|
||||
],
|
||||
'title' =>
|
||||
[
|
||||
'type' => 'text'
|
||||
],
|
||||
'description' =>
|
||||
[
|
||||
'type' => 'text'
|
||||
],
|
||||
'keywords' =>
|
||||
[
|
||||
'type' => 'text'
|
||||
],
|
||||
'mime' =>
|
||||
[
|
||||
'type' => 'text'
|
||||
],
|
||||
'code' =>
|
||||
[
|
||||
'type' => 'integer'
|
||||
],
|
||||
'size' =>
|
||||
[
|
||||
'type' => 'integer'
|
||||
],
|
||||
'time' =>
|
||||
[
|
||||
'type' => 'integer'
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
echo sprintf(
|
||||
'index "%s" created: %s' . PHP_EOL,
|
||||
$config->manticore->index->document,
|
||||
print_r(
|
||||
$result,
|
||||
true
|
||||
)
|
||||
);
|
68
src/config.json
Normal file
68
src/config.json
Normal file
@ -0,0 +1,68 @@
|
||||
{
|
||||
"manticore":
|
||||
{
|
||||
"server":
|
||||
{
|
||||
"host":"127.0.0.1",
|
||||
"port":9308
|
||||
},
|
||||
"index":
|
||||
{
|
||||
"document":"yo_document"
|
||||
}
|
||||
},
|
||||
"cli":
|
||||
{
|
||||
"document":
|
||||
{
|
||||
"crawl":
|
||||
{
|
||||
"queue":
|
||||
{
|
||||
"limit":1
|
||||
},
|
||||
"selector":
|
||||
{
|
||||
"a:not([rel=nofollow])":
|
||||
{
|
||||
"attribute":"href",
|
||||
"external":false,
|
||||
"regex":"/.*/ui"
|
||||
},
|
||||
"image":
|
||||
{
|
||||
"attribute":"src",
|
||||
"external":true,
|
||||
"regex":"/.*/ui"
|
||||
},
|
||||
"audio":
|
||||
{
|
||||
"attribute":"src",
|
||||
"external":false,
|
||||
"regex":"/.*/ui"
|
||||
},
|
||||
"video":
|
||||
{
|
||||
"attribute":"src",
|
||||
"external":false,
|
||||
"regex":"/.*/ui"
|
||||
},
|
||||
"script":
|
||||
{
|
||||
"attribute":"href",
|
||||
"external":false,
|
||||
"regex":"/.*/ui"
|
||||
}
|
||||
}
|
||||
},
|
||||
"snap":
|
||||
{
|
||||
"mime":
|
||||
[
|
||||
"text/html",
|
||||
"image/webp"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
0
src/nodes.json
Normal file
0
src/nodes.json
Normal file
Loading…
Reference in New Issue
Block a user