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,找不到

Preview

则返回 null, * 否则提取所有 Key: Value 为小写蛇形 key=>value 数组 */ protected function parseHtml(string $html): ?array { $doc = new \DOMDocument(); libxml_use_internal_errors(true); $doc->loadHTML('' . $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; } }