搬家调试
parent
539b3b5301
commit
ee1fc98817
@ -0,0 +1,483 @@
|
||||
<?php
|
||||
/**
|
||||
* Chinabrands链接复制和商品复制
|
||||
*/
|
||||
|
||||
class FetchChinabrandsClient extends BaseFetchClient {
|
||||
private $proxyCurl;
|
||||
private static $productHtml;
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->proxyCurl = new ProxyCenterCurl(FetchClientConst::collectTaskSourceChinabrands, false);
|
||||
}
|
||||
|
||||
private function getProductHtml($site, $productId, $goodsId, $wid) {
|
||||
if (!empty(self::$productHtml[$productId])) {
|
||||
return self::$productHtml[$productId];
|
||||
}
|
||||
|
||||
$isCn = $site == 'cn' ? true : false;
|
||||
$site = self::getSiteRealMap($site, 1);
|
||||
$itemUrl = self::buildMoveSourceUrl($site, $productId, $goodsId, $wid);
|
||||
for ($retry = 1; $retry <= 3; $retry++) {
|
||||
$itemInfo = $this->proxyCurl->get($itemUrl);
|
||||
if (empty($itemInfo)) { // 返回长度异常
|
||||
continue;
|
||||
}
|
||||
|
||||
$curlInfo = $this->proxyCurl->getInfo();
|
||||
if (!empty($itemInfo) && ($curlInfo['after']['http_code'] == 200)) {
|
||||
// 获取csrf参数
|
||||
preg_match('/<meta\sname="csrf-param"\scontent="(.*)">/siU', $itemInfo, $csrfParamMatch);
|
||||
preg_match('/<meta\sname="csrf-token"\scontent="(.*)">/siU', $itemInfo, $csrfTokenMatch);
|
||||
$csrfParam = !empty($csrfParamMatch[1]) ? trim($csrfParamMatch[1]) : '';
|
||||
$csrfToken = !empty($csrfTokenMatch[1]) ? trim($csrfTokenMatch[1]) : '';
|
||||
self::$productHtml[$productId] = array(
|
||||
$isCn,
|
||||
$site,
|
||||
$csrfParam,
|
||||
$csrfToken,
|
||||
$itemInfo,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return self::$productHtml[$productId];
|
||||
}
|
||||
|
||||
public function getProductDetail($site, $productId) {
|
||||
if (empty($productId)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
list($productId, $goodsId, $wid) = explode('_', $productId);
|
||||
// 获得详情页内容
|
||||
list($isCn, $site, $csrfParam, $csrfToken, $productHtml) = $this->getProductHtml($site, $productId, $goodsId, $wid);
|
||||
list($title, $adContent) = $this->getProductTitleAndAdContent($productHtml); // 获得标题及描述
|
||||
$productConf = $this->getProductConf($site, $goodsId, $wid, $csrfParam, $csrfToken);
|
||||
list($colorMap, $sizeMap) = $this->getProductColorMapAndSizeMap($productConf);
|
||||
$itemNum = $this->getItemNumByHtml($productHtml, $isCn); // 获得商品货号
|
||||
$price = ZcNumberHelper::numFormat($productConf['price']); // 获得原价
|
||||
$salePrice = isset($productConf['clearance_price']) ? $productConf['clearance_price'] : $productConf['price'];
|
||||
$salePrice = ZcNumberHelper::numFormat($salePrice); // 获得折扣后价格
|
||||
$sourceAttrsAndNotesHtml = $this->getSourceAttrsAndNotesHtml($productHtml, $goodsId, $isCn);
|
||||
list($sourceAttrs, $notesHead) = $this->getProductSourceAttrs($sourceAttrsAndNotesHtml); // 获得产品属性
|
||||
$notes = $this->getProductNotes($notesHead, $productHtml); // 获得图片详情
|
||||
list($skuMap, $stock) = $this->buildProductSkuMap($productConf, $csrfParam, $csrfToken); // 重构sku
|
||||
list($imagePaths, $thumbnails) = $this->getProductImagePaths($productHtml); // 获得组图及缩略图
|
||||
$brandName = isset($sourceAttrs['Brand']) ? $sourceAttrs['Brand'] : ''; // 品牌
|
||||
$saleStatus = isset($productConf['goods_status']) ? FetchClientConst::wareOnline : FetchClientConst::wareUnknown; // 获得销售状态
|
||||
$addWareData = array (
|
||||
'title' => $title,
|
||||
'ad_content' => $adContent,
|
||||
'item_num' => $itemNum,
|
||||
'jd_price' => $price,
|
||||
'sale_price' => $salePrice,
|
||||
'stock_num' => $stock,
|
||||
'weight' => '',
|
||||
'image_paths' => $imagePaths,
|
||||
'thumbnails' => $thumbnails,
|
||||
'skuMap' => $skuMap,
|
||||
'colorMap' => $colorMap,
|
||||
'sizeMap' => $sizeMap,
|
||||
'sourceAttrs' => $sourceAttrs,
|
||||
'notes' => $notes,
|
||||
'cateList' => array(),
|
||||
'brand_name' => $brandName,
|
||||
'sale_status' => $saleStatus,
|
||||
'currency' => FetchClientConst::currencyCodeUSD
|
||||
);
|
||||
|
||||
return $addWareData;
|
||||
}
|
||||
|
||||
// 获得标题及描述
|
||||
private function getProductTitleAndAdContent($productHtml) {
|
||||
preg_match('/<h3\sclass="goods-title(.*)>(.*)<\/h3>/siU', $productHtml, $matchTitle);
|
||||
$title = trim(strip_tags(html_entity_decode($matchTitle[2])));
|
||||
preg_match('/<META\s+name="description"\s+content="([\w\W]*?)"/si', $productHtml, $matchContent);
|
||||
$adContent = trim(str_replace('Chinabrands.com:', '', strip_tags(html_entity_decode($matchContent[1]))));
|
||||
return array($title, $adContent);
|
||||
}
|
||||
|
||||
// 获得货号
|
||||
private function getItemNumByHtml($productHtml, $isCn) {
|
||||
if($isCn) {
|
||||
preg_match('/<span\sclass="sku\sfb">SKU:\s(.*)<\/span>/siU', $productHtml, $itemNumMatch);
|
||||
$itemNum = !empty($itemNumMatch) && is_array($itemNumMatch) ? trim($itemNumMatch[1]) : '';
|
||||
} else {
|
||||
preg_match('/<span\sclass="sku"><span\sclass="c6">(.*)<\/span>(.*)<\/span>/siU', $productHtml, $itemNumMatch);
|
||||
$itemNum = !empty($itemNumMatch) && is_array($itemNumMatch) ? trim($itemNumMatch[2]) : '';
|
||||
}
|
||||
|
||||
return $itemNum;
|
||||
}
|
||||
|
||||
// 获得颜色尺码
|
||||
private function getProductColorMapAndSizeMap($productConf) {
|
||||
$colorMap = array();
|
||||
$sizeMap = array();
|
||||
if(empty($productConf['attr_data'])) {
|
||||
return array(
|
||||
$colorMap,
|
||||
$sizeMap
|
||||
);
|
||||
}
|
||||
|
||||
$attrData = $productConf['attr_data'];
|
||||
if(!empty($attrData['Color'])) {
|
||||
foreach ($attrData['Color'] as $val) {
|
||||
if(empty($val)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$imgUrls = array($val['image']);
|
||||
$colorMap[$val['value']] = array(
|
||||
'name' => $val['value'],
|
||||
'imgUrls' => $imgUrls,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if(!empty($attrData['Size'])) {
|
||||
foreach ($attrData['Size'] as $val) {
|
||||
if(empty($val)) {
|
||||
continue;
|
||||
}
|
||||
$sizeMap[$val['value']] = array(
|
||||
'name' => $val['value'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
$colorMap,
|
||||
$sizeMap
|
||||
);
|
||||
}
|
||||
|
||||
// 构造sku
|
||||
private function buildProductSkuMap($productConf, $csrfParam, $csrfToken) {
|
||||
$stockNum = 0;
|
||||
$priceStockMap = array();
|
||||
if(empty($productConf['attr_data'])) {
|
||||
return $priceStockMap;
|
||||
}
|
||||
|
||||
$attrData = $productConf['attr_data'];
|
||||
$colorMap = isset($attrData['Color']) ? $attrData['Color'] : array();
|
||||
$sizeMap = isset($attrData['Size']) ? $attrData['Size'] : array();
|
||||
if(!empty($colorMap) && !empty($sizeMap)) {
|
||||
list($priceStockMap, $stockNum) = $this->buildProductColorAndSizeSkuMap($colorMap, $sizeMap, $csrfParam, $csrfToken);
|
||||
} elseif (!empty($colorMap)) {
|
||||
list($priceStockMap, $stockNum) = $this->buildProductColorSkuMap($colorMap, $productConf);
|
||||
}
|
||||
|
||||
return array(
|
||||
$priceStockMap,
|
||||
$stockNum,
|
||||
);
|
||||
}
|
||||
|
||||
private function buildProductColorAndSizeSkuMap($colorMap, $sizeMap, $csrfParam, $csrfToken) {
|
||||
$stockNum = 0;
|
||||
$priceStockMap = array();
|
||||
foreach($colorMap as $colorVal) {
|
||||
$currentSizeMap = $sizeMap;
|
||||
$colorName = $colorVal['value'];
|
||||
$colorDisplay = $colorVal['display'];
|
||||
$colorTempProductConf = array();
|
||||
if($colorDisplay == 1 && $colorVal['selected'] != 1) {
|
||||
// 异步调用价格接口
|
||||
$colorTempProductConf = $this->getProductConfByUrl($colorVal['url'], $csrfParam, $csrfToken);
|
||||
$currentSizeMap = !empty($colorTempProductConf['attr_data']['Size']) ? $colorTempProductConf['attr_data']['Size'] : array();
|
||||
}
|
||||
|
||||
foreach($currentSizeMap as $sizeVal) {
|
||||
if($colorDisplay != 1 || $sizeVal['display'] != 1) {
|
||||
$priceStockMap[';' . $colorName . ';' . $sizeVal['value'] . ';'] = array(
|
||||
'price' => 0,
|
||||
'stock' => 0,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if($sizeVal['selected'] != 1) {
|
||||
// 异步调用价格接口
|
||||
$tempProductConf = $this->getProductConfByUrl($sizeVal['url'], $csrfParam, $csrfToken);
|
||||
list($price, $stock) = $this->getCurrentPriceAndStock($tempProductConf);
|
||||
$priceStockMap[';' . $colorName . ';' . $sizeVal['value'] . ';'] = array(
|
||||
'price' => $price,
|
||||
'stock' => $stock,
|
||||
);
|
||||
} else {
|
||||
list($price, $stock) = $this->getCurrentPriceAndStock($colorTempProductConf);
|
||||
$priceStockMap[';' . $colorName . ';' . $sizeVal['value'] . ';'] = array(
|
||||
'price' => $price,
|
||||
'stock' => $stock,
|
||||
);
|
||||
}
|
||||
|
||||
$stockNum += $stock;
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
$priceStockMap,
|
||||
$stockNum
|
||||
);
|
||||
}
|
||||
|
||||
private function buildProductColorSkuMap($colorMap, $productConf, $csrfParam, $csrfToken) {
|
||||
$stockNum = 0;
|
||||
$priceStockMap = array();
|
||||
foreach ($colorMap as $colorVal) {
|
||||
$colorName = $colorVal['value'];
|
||||
$colorDisplay = $colorVal['display'];
|
||||
if($colorDisplay == 3) {
|
||||
$priceStockMap[';' . $colorName . ';;'] = array(
|
||||
'price' => 0,
|
||||
'stock' => 0,
|
||||
);
|
||||
} elseif ($colorVal['selected'] == 1) {
|
||||
list($price, $stock) = $this->getCurrentPriceAndStock($productConf);
|
||||
$priceStockMap[';' . $colorName . ';;'] = array(
|
||||
'price' => $price,
|
||||
'stock' => $stock,
|
||||
);
|
||||
$stockNum += $stock;
|
||||
} else {
|
||||
// 异步调用价格接口
|
||||
$colorTempProductConf = $this->getProductConfByUrl($colorVal['url'], $csrfParam, $csrfToken);
|
||||
list($price, $stock) = $this->getCurrentPriceAndStock($colorTempProductConf);
|
||||
$priceStockMap[';' . $colorName . ';;'] = array(
|
||||
'price' => $price,
|
||||
'stock' => $stock,
|
||||
);
|
||||
$stockNum += $stock;
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
$priceStockMap,
|
||||
$stockNum
|
||||
);
|
||||
}
|
||||
|
||||
// 获取商品配置的价格和库存
|
||||
private function getCurrentPriceAndStock($productConf) {
|
||||
$price = isset($productConf['clearance_price']) ? $productConf['clearance_price'] : $productConf['price'];
|
||||
$price = ZcNumberHelper::numFormat($price);
|
||||
$stock = empty($productConf['sum_all_warehouse_stock']) ? $productConf['sum_all_warehouse_stock'] : $productConf['goods_number'];
|
||||
$stock = intval($stock);
|
||||
return array(
|
||||
$price,
|
||||
$stock
|
||||
);
|
||||
}
|
||||
|
||||
// 异步获取商品配置
|
||||
private function getProductConf($site, $goodsId, $wid, $csrfParam, $csrfToken) {
|
||||
$params = array(
|
||||
$csrfParam => $csrfToken,
|
||||
'goods_id' => intval($goodsId),
|
||||
'wid' => intval($wid),
|
||||
'type' => '',
|
||||
'activity_from' => ''
|
||||
);
|
||||
$priceConf = $this->proxyCurl->post('https://' . $site . '/goods/ajax-goods.html', $params);
|
||||
return !empty($priceConf) ? json_decode($priceConf, TRUE)['data'] : [];
|
||||
}
|
||||
|
||||
private function getProductConfByUrl($url, $csrfParam, $csrfToken) {
|
||||
$urlConf = parse_url($url);
|
||||
parse_str($urlConf['query'], $params);
|
||||
$productConf = $this->getProductConf($urlConf['host'], $params['goods_id'], $params['wid'], $csrfParam, $csrfToken);
|
||||
return $productConf;
|
||||
}
|
||||
|
||||
// 获得图片组图
|
||||
private function getProductImagePaths($productHtml) {
|
||||
$pattern = '/<div\s+class="thumb-pic">(.*)<img\s+class="lazyimg"(.*)data-original="(.*)"(.*)data-big-url="(.*)"/siU';
|
||||
preg_match_all($pattern, $productHtml, $matchImages);
|
||||
return array(
|
||||
$matchImages[5],
|
||||
$matchImages[3],
|
||||
);
|
||||
}
|
||||
|
||||
// 获得内容详情
|
||||
private function getSourceAttrsAndNotesHtml($productHtml, $goodsId, $isCn) {
|
||||
$attrsAndNotesHtml = '';
|
||||
if($isCn) {
|
||||
// 异步获取内容详情
|
||||
$params = array(
|
||||
'goods_id' => $goodsId,
|
||||
);
|
||||
$attrsAndNotesRes = $this->proxyCurl->post('https://www.chinabrands.cn/goods/ajax-goods-description.html', $params);
|
||||
$attrsAndNotesHtml = !empty($attrsAndNotesRes) ? json_decode($attrsAndNotesRes, TRUE) : '';
|
||||
$attrsAndNotesHtml = !empty($attrsAndNotesHtml['data']['goods_desc']) ? $attrsAndNotesHtml['data']['goods_desc'] : '';
|
||||
} else {
|
||||
preg_match('/<div\s+class="j_detailPDMData">(.*)<\/div>\s+<div\s+class="comment-wrap/isU', $productHtml, $attrsAndNotesHtml);
|
||||
$attrsAndNotesHtml = !empty($attrsAndNotesHtml[1]) ? $attrsAndNotesHtml[1] : '';
|
||||
}
|
||||
|
||||
return $attrsAndNotesHtml;
|
||||
}
|
||||
|
||||
// 获得商品属性
|
||||
private function getProductSourceAttrs($sourceAttrsAndNotesHtml) {
|
||||
$sourceAttrs = array();
|
||||
preg_match('/<div\s+class="xkclear"><\/div>\s?<div\s+class="xxkkk">(.*)<div\s+class="xkclear">/isU', $sourceAttrsAndNotesHtml, $attrHtml);
|
||||
preg_match_all('/<strong>(.*):<\/strong>(.*)</isU', $attrHtml[0], $attrMatch);
|
||||
if (empty($attrMatch[1]) || empty($attrMatch[2]) || count($attrMatch[1]) != count($attrMatch[2])) {
|
||||
return array();
|
||||
}
|
||||
|
||||
foreach ($attrMatch[1] as $key => $val) {
|
||||
$sourceAttrs[trim($val)] = trim($attrMatch[2][$key]);
|
||||
}
|
||||
|
||||
$notesHead = str_replace($attrHtml[1], '', $sourceAttrsAndNotesHtml);
|
||||
return array(
|
||||
$sourceAttrs,
|
||||
$notesHead
|
||||
);
|
||||
}
|
||||
|
||||
// 获得商品详情
|
||||
private function getProductNotes($notesHead, $productHtml) {
|
||||
$notes = $notesHead;
|
||||
preg_match('/<div\s+class="size-chart-container(.*)<!--\srender/isU', $productHtml, $containerMatch);
|
||||
if(!empty($containerMatch[0])) {
|
||||
$container = str_replace('<!-- render', '', $containerMatch[0]);
|
||||
$notes .= trim($container);
|
||||
}
|
||||
|
||||
$notes = !empty($notes) ? $notes : '<p> </p>';
|
||||
return $notes;
|
||||
}
|
||||
|
||||
// 根据平台获取时,获取商品列表
|
||||
public function getWebsiteProductList($site, $source, $pageSize, $pageNo, $keyword, $sortValue, $minPrice, $maxPrice) {
|
||||
$site = self::getSiteRealMap($site, 1);
|
||||
$url = sprintf('https://%s/dropshipping-%s', $site, $keyword);
|
||||
$url = $pageNo > 1 ? $url . '_page' . $pageNo . '.html?' : $url . '.html?';
|
||||
$params = array();
|
||||
$params['searchUrl'] = '%2Fsearch%2Fkeywords-notice.html';
|
||||
if($sortValue) {
|
||||
$sort = FetchClientTool::getWebsiteCopySortValue($source, $sortValue);
|
||||
$params['odr'] = $sort;
|
||||
}
|
||||
if($pageSize && $pageSize > 40) {
|
||||
$params['pagesize'] = $pageSize;
|
||||
}
|
||||
if($minPrice && $maxPrice) {
|
||||
$params['min_price='] = $minPrice;
|
||||
$params['max_price='] = $maxPrice;
|
||||
} elseif($minPrice) {
|
||||
$params['min_price='] = $minPrice;
|
||||
$params['max_price='] = 1000000;
|
||||
} elseif($maxPrice) {
|
||||
$params['min_price='] = 0.01;
|
||||
$params['max_price='] = $maxPrice;
|
||||
}
|
||||
|
||||
$productList = array();
|
||||
$productTotal = 0;
|
||||
$url = $url . http_build_query($params);
|
||||
for ($retry = 1; $retry <= 3; $retry++) {
|
||||
$websiteHtml = $this->proxyCurl->get($url);
|
||||
if (empty($websiteHtml)) { // 返回长度异常
|
||||
continue;
|
||||
}
|
||||
|
||||
$curlInfo = $this->proxyCurl->getInfo();
|
||||
if (!empty($websiteHtml) && ($curlInfo['after']['http_code'] == 200)) {
|
||||
preg_match('/<ul\s+class="pagination">(.*)<li>(\D*)(\d*)(\D*)(\d*)(\D*)<\/li>/isU', $websiteHtml, $pageHtmlMatch);
|
||||
$productTotal = $pageHtmlMatch[3];
|
||||
$productList = $this->parseShopProductList($websiteHtml);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return array (
|
||||
$productList,
|
||||
$productTotal
|
||||
);
|
||||
}
|
||||
|
||||
private function parseShopProductList($html) {
|
||||
$productList = array();
|
||||
$rule = '/<li(.*)>(.*)<img\s+class="lazyimg"(.*)data-original="(.*)"(.*)';
|
||||
$rule .= '<div\s+class="goods-title">(.*)<a\s+href="(.*)"\s+data-promid="(.*)">(.*)<\/a>(.*)<\/div>(.*)';
|
||||
$rule .= '<span\s+class="my_shop_price"(.*)data-orgp="(.*)">(.*)<\/li>/isU';
|
||||
preg_match_all($rule, $html, $goodListMatch);
|
||||
$imgUrls = $goodListMatch[4];
|
||||
$titles = $goodListMatch[9];
|
||||
$productUrls = $goodListMatch[7];
|
||||
$productUrlparams = $goodListMatch[8];
|
||||
$prices = $goodListMatch[13];
|
||||
|
||||
foreach ($imgUrls as $key => $imgUrl) {
|
||||
$productUrl = $productUrls[$key] . '?' . $productUrlparams[$key];
|
||||
list(, $productId) = self::getSiteAndSourceItemIdByUrl($productUrl);
|
||||
|
||||
$productList[$productId] = array(
|
||||
'productId' => $productId,
|
||||
'jd_price' => ZcNumberHelper::numFormat($prices[$key]),
|
||||
'title' => trim(strip_tags(html_entity_decode($titles[$key]))),
|
||||
'imgUrl' => $imgUrl
|
||||
);
|
||||
}
|
||||
|
||||
return $productList;
|
||||
}
|
||||
|
||||
// 封装来源链接
|
||||
public static function buildMoveSourceUrl($site, $productId, $goodsId, $wid) {
|
||||
if($site == 'www.chinabrands.cn') {
|
||||
$sourceUrl = sprintf('https://%s/item/%s-p.html?goods_id=%s&wid=%s', $site, $productId, $goodsId, $wid);
|
||||
} else {
|
||||
$sourceUrl = sprintf('https://%s/item/dropship-a-%s-p.html?goods_id=%s&wid=%s', $site, $productId, $goodsId, $wid);
|
||||
}
|
||||
return $sourceUrl;
|
||||
}
|
||||
|
||||
// 获取rule
|
||||
public static function getSourceRule() {
|
||||
return '/(?:https:\/\/|http:\/\/)(.*\.chinabrands\..*)+\/item\/(.*-(\w+)|(\w+))-p\.html\?goods_id=(\d+)&wid=(\d+)/i';
|
||||
}
|
||||
|
||||
// 通过链接获取
|
||||
public static function getSiteAndSourceItemIdByUrl($url) {
|
||||
preg_match(self::getSourceRule(), $url, $matchs);
|
||||
$site = $matchs[1];
|
||||
if($site == 'www.chinabrands.cn') {
|
||||
$sourceItemId = implode('_', array_slice($matchs, -3, 3));
|
||||
} else {
|
||||
$sourceItemId = implode('_', array($matchs[3], $matchs[5], $matchs[6]));
|
||||
}
|
||||
|
||||
return [
|
||||
$site,
|
||||
$sourceItemId,
|
||||
];
|
||||
}
|
||||
|
||||
public static function getSiteRealMap($site, $flip = 0) {
|
||||
$siteMap = array(
|
||||
'www.chinabrands.cn' => 'cn',
|
||||
'www.chinabrands.com' => 'en',
|
||||
'de.chinabrands.com' => 'de',
|
||||
'fr.chinabrands.com' => 'fr',
|
||||
'es.chinabrands.com' => 'es',
|
||||
'ru.chinabrands.com' => 'ru',
|
||||
'it.chinabrands.com' => 'it'
|
||||
);
|
||||
|
||||
$mapArr = $flip ? array_flip($siteMap) : $siteMap;
|
||||
return !empty($mapArr[$site]) ? $mapArr[$site] : $site;
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue