Files
useds/app/command/EmailParseCommand.php
T

144 lines
4.2 KiB
PHP
Raw Normal View History

2026-01-05 14:33:21 +08:00
<?php
namespace app\command;
use support\Db;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* 将拉取邮箱的详情进行清洗,找出询盘的邮件入库(根据emai_detail)
*/
class EmailParseCommand extends Command
{
protected static $defaultName = 'email-parse';
protected static $defaultDescription = '邮件清洗';
protected function configure()
{
// 不再接收参数,固定每次 100 条
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$batchSize = 100; // 每次处理 100 条
$page = 0;
// 明确定义 parsed_contacts 表里要插入的所有列(顺序无所谓,但要全部包含)
$fields = [
'email_id',
'date',
'email',
'name',
'tel',
'company',
'country',
'form_url',
'message',
'ip',
'iplocation',
'ilang',
'mailto',
'navigator_language',
'navigator_platform',
'screen_width',
'screen_height',
'ucountry',
];
while (true) {
$rows = Db::table('emails')
->orderBy('id', 'asc')
->limit($batchSize)
->offset($page * $batchSize)
->get();
if ($rows->isEmpty()) {
break;
}
$toInsert = [];
foreach ($rows as $row) {
$parsed = $this->parseHtml($row->body);
if ($parsed === null) {
$output->writeln(" ↳ [{$row->id}] skip: not a contact-form HTML");
continue;
}
// 严格按 $fields 顺序填充每条记录,缺失的字段补 null
$record = [];
foreach ($fields as $col) {
if ($col === 'email_id') {
$record[$col] = $row->id;
} else {
$record[$col] = $parsed[$col] ?? null;
}
}
$toInsert[] = $record;
}
if (!empty($toInsert)) {
Db::table('email_parsed_result')->insert($toInsert);
$output->writeln("Inserted batch #" . ($page + 1) . ", count: " . count($toInsert));
}
$page++;
}
$output->writeln("✔ All done.");
return Command::SUCCESS;
}
/**
* 解析 HTML,找不到 <h2>Preview</h2> 则返回 null,
* 否则提取所有 <span>Key:</span> Value 为小写蛇形 key=>value 数组
*/
protected function parseHtml(string $html): ?array
{
$doc = new \DOMDocument();
libxml_use_internal_errors(true);
$doc->loadHTML('<?xml encoding="utf-8"?>' . $html);
libxml_clear_errors();
$xpath = new \DOMXPath($doc);
// 类型检查
if ($xpath->query("//h2[text()='Preview']")->length === 0) {
return null;
}
$nodes = $xpath->query("//div[span]");
$data = [];
/** @var \DOMElement $node */
foreach ($nodes as $node) {
$span = $xpath->query("span", $node)->item(0);
if (! $span) {
continue;
}
// 原始键名
$key = trim(str_replace(':', '', $span->textContent));
// 转成小写蛇形
$col = strtolower(str_replace(' ', '_', $key));
// 获取值
$a = $xpath->query(".//a", $node)->item(0);
if ($a && $a->hasAttribute('href')) {
$value = trim($a->getAttribute('href'));
} else {
$clone = $node->cloneNode(true);
$spanClone = $xpath->query(".//span", $clone)->item(0);
if ($spanClone) {
$spanClone->parentNode->removeChild($spanClone);
}
$value = trim($clone->textContent);
}
$data[$col] = $value;
}
return $data;
}
}