抓取车辆
This commit is contained in:
@@ -7,6 +7,7 @@ use app\common\service\UsedCarDetailService;
|
|||||||
use app\common\service\OssService;
|
use app\common\service\OssService;
|
||||||
use support\Request;
|
use support\Request;
|
||||||
use support\exception\BusinessException;
|
use support\exception\BusinessException;
|
||||||
|
use support\Log;
|
||||||
|
|
||||||
class UsedCarDetailController extends BaseController
|
class UsedCarDetailController extends BaseController
|
||||||
{
|
{
|
||||||
@@ -315,4 +316,41 @@ class UsedCarDetailController extends BaseController
|
|||||||
return $this->error($e->getMessage());
|
return $this->error($e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抓取懂车帝二手车数据并保存
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return \support\Response
|
||||||
|
*/
|
||||||
|
public function crawlAndStore(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$url = $request->post('url');
|
||||||
|
|
||||||
|
if (empty($url)) {
|
||||||
|
throw new BusinessException('链接不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证是否是懂车帝链接
|
||||||
|
if (!str_contains($url, 'dongchedi.com')) {
|
||||||
|
throw new BusinessException('只支持懂车帝二手车链接');
|
||||||
|
}
|
||||||
|
|
||||||
|
$userId = $request->user['id'] ?? 0;
|
||||||
|
if (empty($userId)) {
|
||||||
|
throw new BusinessException('用户未登录');
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->service->crawlAndStore($url, $userId);
|
||||||
|
|
||||||
|
return $this->success($result, '抓取成功,已保存为待审核状态');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::error('抓取懂车帝二手车失败', [
|
||||||
|
'url' => $request->post('url'),
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
return $this->error($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,8 @@ class UploadService
|
|||||||
$extensionFromPath = pathinfo($basename, PATHINFO_EXTENSION);
|
$extensionFromPath = pathinfo($basename, PATHINFO_EXTENSION);
|
||||||
$extension = $extensionFromName ?: $extensionFromPath;
|
$extension = $extensionFromName ?: $extensionFromPath;
|
||||||
|
|
||||||
$mime = mime_content_type($filePath) ?: 'application/octet-stream';
|
// 获取 MIME 类型,如果函数不可用则使用扩展名判断
|
||||||
|
$mime = $this->getMimeType($filePath, $extension);
|
||||||
$size = filesize($filePath) ?: 0;
|
$size = filesize($filePath) ?: 0;
|
||||||
|
|
||||||
$sceneUrl = $scene ? ($scene . '/') : '';
|
$sceneUrl = $scene ? ($scene . '/') : '';
|
||||||
@@ -116,6 +117,49 @@ class UploadService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文件的 MIME 类型
|
||||||
|
* 优先使用 mime_content_type(),不可用时根据扩展名判断
|
||||||
|
*
|
||||||
|
* @param string $filePath 文件路径
|
||||||
|
* @param string $extension 文件扩展名
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getMimeType(string $filePath, string $extension = ''): string
|
||||||
|
{
|
||||||
|
// 优先使用系统函数
|
||||||
|
if (function_exists('mime_content_type')) {
|
||||||
|
$mime = mime_content_type($filePath);
|
||||||
|
if ($mime !== false && $mime !== '') {
|
||||||
|
return $mime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 降级:根据扩展名判断
|
||||||
|
$extension = strtolower(ltrim($extension, '.'));
|
||||||
|
$mimeTypes = [
|
||||||
|
'jpg' => 'image/jpeg',
|
||||||
|
'jpeg' => 'image/jpeg',
|
||||||
|
'png' => 'image/png',
|
||||||
|
'gif' => 'image/gif',
|
||||||
|
'webp' => 'image/webp',
|
||||||
|
'bmp' => 'image/bmp',
|
||||||
|
'svg' => 'image/svg+xml',
|
||||||
|
'pdf' => 'application/pdf',
|
||||||
|
'doc' => 'application/msword',
|
||||||
|
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||||
|
'xls' => 'application/vnd.ms-excel',
|
||||||
|
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
|
'zip' => 'application/zip',
|
||||||
|
'rar' => 'application/x-rar-compressed',
|
||||||
|
'mp4' => 'video/mp4',
|
||||||
|
'mp3' => 'audio/mpeg',
|
||||||
|
'txt' => 'text/plain',
|
||||||
|
];
|
||||||
|
|
||||||
|
return $mimeTypes[$extension] ?? 'application/octet-stream';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除 OSS 上的文件
|
* 删除 OSS 上的文件
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -464,11 +464,11 @@ class UsedCarDetailService
|
|||||||
->where('used_car_id', $carId)
|
->where('used_car_id', $carId)
|
||||||
->where('status', 1)
|
->where('status', 1)
|
||||||
->first();
|
->first();
|
||||||
if ($existing) {
|
// if ($existing) {
|
||||||
$msg = '车辆已推送,跳过';
|
// $msg = '车辆已推送,跳过';
|
||||||
Log::info('WordPress推送:' . $msg, ['car_id' => $carId]);
|
// Log::info('WordPress推送:' . $msg, ['car_id' => $carId]);
|
||||||
return ['success' => true, 'message' => $msg];
|
// return ['success' => true, 'message' => $msg];
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 调用推送服务
|
// 调用推送服务
|
||||||
$wpService = new \app\common\service\WordpressSiteService();
|
$wpService = new \app\common\service\WordpressSiteService();
|
||||||
@@ -793,8 +793,9 @@ class UsedCarDetailService
|
|||||||
{
|
{
|
||||||
$result = [];
|
$result = [];
|
||||||
|
|
||||||
// 从 car_info 直接获取的字段
|
// 从 car_info 直接获取的字段,清理掉"万"等字符
|
||||||
$result['official_guide_price'] = $carInfo['official_price'] ?? '';
|
$officialPrice = $carInfo['official_price'] ?? '';
|
||||||
|
$result['official_guide_price'] = $this->convertPriceToYuan($officialPrice);
|
||||||
|
|
||||||
// 1. 基本信息
|
// 1. 基本信息
|
||||||
$result['manufacturer'] = $this->getFieldValue($groups, 'sub_brand_name') ?? '';
|
$result['manufacturer'] = $this->getFieldValue($groups, 'sub_brand_name') ?? '';
|
||||||
@@ -900,4 +901,118 @@ class UsedCarDetailService
|
|||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 抓取懂车帝二手车数据并保存
|
||||||
|
*
|
||||||
|
* @param string $url 懂车帝二手车链接
|
||||||
|
* @param int $userId 用户ID
|
||||||
|
* @return array 返回创建的车辆数据
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function crawlAndStore(string $url, int $userId): array
|
||||||
|
{
|
||||||
|
// 1. 抓取二手车基本信息
|
||||||
|
$usedCarCrawl = new \app\common\utils\crawl\service\UsedCarCrawlSingle();
|
||||||
|
$info = $usedCarCrawl->crawlCar($url);
|
||||||
|
|
||||||
|
// 2. 如果有 car_id,抓取详细参数
|
||||||
|
$params = [];
|
||||||
|
if (!empty($info['car_id'])) {
|
||||||
|
try {
|
||||||
|
$params = $this->crawlParams($info['car_id']);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::error('抓取车辆参数失败', [
|
||||||
|
'car_id' => $info['car_id'],
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
// 参数抓取失败不影响主流程
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 构建数据库记录数据
|
||||||
|
// 处理图片:如果是数组,转为逗号分隔字符串
|
||||||
|
$imagesValue = $info['image'] ?? '';
|
||||||
|
if (is_array($imagesValue)) {
|
||||||
|
$imagesValue = implode(',', array_filter($imagesValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'code' => $this->generateCode(),
|
||||||
|
'user_id' => $userId,
|
||||||
|
'product_name' => $info['title'] ?? $info['car_name'] ?? '',
|
||||||
|
'price' => $this->convertPriceToYuan($info['price'] ?? 0),
|
||||||
|
'source_place' => $info['source_place'] ?? '',
|
||||||
|
'plate_time' => $info['plate_time'] ?? '',
|
||||||
|
'displacement' => $info['capacity'] ?? '',
|
||||||
|
'mileage' => $this->convertMileageToKm($info['usage_mileage'] ?? 0),
|
||||||
|
'body_color' => $info['body_color'] ?? '',
|
||||||
|
'condition_desc' => $info['condition_desc'] ?? '',
|
||||||
|
'source_url' => $url,
|
||||||
|
'crawl_title' => $info['title'] ?? '',
|
||||||
|
'crawl_brand' => $info['brand_name'] ?? '',
|
||||||
|
'crawl_brand_id' => $info['brand_id'] ?? 0,
|
||||||
|
'crawl_series' => $info['series_name'] ?? '',
|
||||||
|
'crawl_series_id' => $info['series_id'] ?? 0,
|
||||||
|
'crawl_year' => $info['year'] ?? '',
|
||||||
|
'crawl_msrp' => $info['new_price'] ?? '',
|
||||||
|
'crawl_car_id' => $info['car_id'] ?? 0,
|
||||||
|
'status' => 0, // 待审核
|
||||||
|
'images' => $imagesValue,
|
||||||
|
'power' => $info['horsepower'] ?? '',
|
||||||
|
];
|
||||||
|
|
||||||
|
// 4. 合并详细参数,过滤掉数组类型的值
|
||||||
|
$params = array_filter($params, function($value) {
|
||||||
|
return !is_array($value);
|
||||||
|
});
|
||||||
|
$data = array_merge($data, $params);
|
||||||
|
|
||||||
|
// 5. 创建记录
|
||||||
|
$car = UsedCarDetail::create($data);
|
||||||
|
|
||||||
|
return $car->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成车辆编码
|
||||||
|
*/
|
||||||
|
private function generateCode(): string
|
||||||
|
{
|
||||||
|
return 'UC' . date('YmdHis') . rand(1000, 9999);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将价格(万)转换为元
|
||||||
|
*/
|
||||||
|
private function convertPriceToYuan($price): float
|
||||||
|
{
|
||||||
|
if (empty($price)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// 如果是数字,直接乘以10000
|
||||||
|
if (is_numeric($price)) {
|
||||||
|
return floatval($price) * 10000;
|
||||||
|
}
|
||||||
|
// 如果是字符串,移除"万"字后乘以10000
|
||||||
|
$price = str_replace('万', '', $price);
|
||||||
|
return floatval($price) * 10000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将里程(万公里)转换为公里
|
||||||
|
*/
|
||||||
|
private function convertMileageToKm($mileage): float
|
||||||
|
{
|
||||||
|
if (empty($mileage)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// 如果是数字,直接乘以10000
|
||||||
|
if (is_numeric($mileage)) {
|
||||||
|
return floatval($mileage) * 10000;
|
||||||
|
}
|
||||||
|
// 如果是字符串,移除"万公里"后乘以10000
|
||||||
|
$mileage = str_replace(['万公里', '万'], '', $mileage);
|
||||||
|
return floatval($mileage) * 10000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1265,6 +1265,7 @@ class WordpressSiteService
|
|||||||
if (str_contains($car->mileage, '万') || str_contains($car->mileage, '万公里')) {
|
if (str_contains($car->mileage, '万') || str_contains($car->mileage, '万公里')) {
|
||||||
$car->mileage = str_replace(['万', '公里', '万公里'],'',$car->mileage)*10000;
|
$car->mileage = str_replace(['万', '公里', '万公里'],'',$car->mileage)*10000;
|
||||||
}
|
}
|
||||||
|
$car->power = str_replace('马力','',$car->power);
|
||||||
foreach ($fieldMap as $acfKey => $label) {
|
foreach ($fieldMap as $acfKey => $label) {
|
||||||
if (!empty($car->$acfKey)) {
|
if (!empty($car->$acfKey)) {
|
||||||
$value = $car->$acfKey;
|
$value = $car->$acfKey;
|
||||||
|
|||||||
@@ -105,10 +105,31 @@ class UsedCarCrawlSingle
|
|||||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
$imagesArray = [];
|
$imagesArray = [];
|
||||||
}
|
}
|
||||||
$saveDir = public_path('uploads/usedcar/' . $info['used_car_id']);
|
|
||||||
$image = $this->uploadAndRemove($imagesArray, $saveDir);
|
Log::info('抓取到的图片数量', [
|
||||||
$info['image'] = $image['image'];
|
'url' => $url,
|
||||||
$info['image_pull'] = $image['image_pull'];
|
'images_count' => count($imagesArray),
|
||||||
|
'images' => $imagesArray,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!empty($imagesArray)) {
|
||||||
|
$saveDir = public_path('uploads/usedcar/' . $info['used_car_id']);
|
||||||
|
$image = $this->uploadAndRemove($imagesArray, $saveDir);
|
||||||
|
if ($image !== false) {
|
||||||
|
$info['image'] = $image['image'];
|
||||||
|
$info['image_pull'] = $image['image_pull'];
|
||||||
|
Log::info('图片上传成功', [
|
||||||
|
'uploaded_count' => count($image['image']),
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$info['image'] = [];
|
||||||
|
$info['image_pull'] = [];
|
||||||
|
Log::warning('图片上传失败', ['url' => $url]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$info['image'] = [];
|
||||||
|
$info['image_pull'] = [];
|
||||||
|
}
|
||||||
|
|
||||||
// 继续派发配置页任务
|
// 继续派发配置页任务
|
||||||
// if (!empty($info['car_id_url'])) {
|
// if (!empty($info['car_id_url'])) {
|
||||||
@@ -382,6 +403,8 @@ class UsedCarCrawlSingle
|
|||||||
public function downloadImageAlbum(array $urls, string $saveDir, string $referer): array
|
public function downloadImageAlbum(array $urls, string $saveDir, string $referer): array
|
||||||
{
|
{
|
||||||
$result = [];
|
$result = [];
|
||||||
|
$successCount = 0;
|
||||||
|
$failCount = 0;
|
||||||
|
|
||||||
foreach ($urls as $url) {
|
foreach ($urls as $url) {
|
||||||
$url = trim((string)$url);
|
$url = trim((string)$url);
|
||||||
@@ -395,13 +418,29 @@ class UsedCarCrawlSingle
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
$path = $this->downloadImage($url, $saveDir, $referer);
|
$path = $this->downloadImage($url, $saveDir, $referer);
|
||||||
|
if ($path !== null) {
|
||||||
|
$successCount++;
|
||||||
|
} else {
|
||||||
|
$failCount++;
|
||||||
|
}
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
|
$failCount++;
|
||||||
|
Log::error('下载图片失败', [
|
||||||
|
'url' => $url,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
$path = null;
|
$path = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$result[$url] = $path;
|
$result[$url] = $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log::info('图片下载完成', [
|
||||||
|
'total' => count($urls),
|
||||||
|
'success' => $successCount,
|
||||||
|
'fail' => $failCount,
|
||||||
|
]);
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user