This commit is contained in:
ZiJIe 2025-07-15 17:39:12 +08:00
parent 1388d39376
commit 6b01a5fbfe
9 changed files with 473 additions and 31 deletions

View File

@ -99,7 +99,15 @@
<failOnMissingWebXml>false</failOnMissingWebXml>
<warName>${project.artifactId}</warName>
</configuration>
</plugin>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>15</source>
<target>15</target>
</configuration>
</plugin>
</plugins>
<finalName>${project.artifactId}</finalName>
</build>

View File

@ -1,17 +0,0 @@
package com.ruoyi.web.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.core.controller.BaseController;
/**
* prodController
*
* @author ruoyi
* @date 2025-06-21
*/
@RestController
@RequestMapping("/prod/products")
public class ProductsController extends BaseController
{
}

View File

@ -0,0 +1,61 @@
package com.ruoyi.web.controller.tool;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;
import java.util.ArrayList;
import java.util.List;
/**
* 1688Controller
*
* @author ruoyi
* @date 2025-07-15
*/
@RestController
@RequestMapping("/prod/ozon")
public class ProductsController extends BaseController
{
@GetMapping("/scrapeImages")
public AjaxResult scrapeImages()
{
String url = "https://www.ozon.ru/highlight/ozon-global/?currency_price=14.000%3B500.000";
List<String> imageUrls = new ArrayList<>();
Site site = Site.me()
.setRetryTimes(3)
.setTimeOut(10000);
site.addHeader("cookie", "xcid=b30f6db523d4aee734a822aa0a230b3f; __Secure-ext_xcid=b30f6db523d4aee734a822aa0a230b3f; __Secure-ab-group=92; rfuid=NjkyNDcyNDUyLDEyNC4wNDM0NzUyNzUxNjA3NCwxMDI4MjM3MjIzLC0xLC05ODc0NjQ3MjQsVzNzaWJtRnRaU0k2SWtOb2NtOXRhWFZ0SUZCRVJpQlFiSFZuYVc0aUxDSmtaWE5qY21sd2RHbHZiaUk2SWxCdmNuUmhZbXhsSUVSdlkzVnRaVzUwSUVadmNtMWhkQ0lzSW0xcGJXVlVlWEJsY3lJNlczc2lkSGx3WlNJNkltRndjR3hwWTJGMGFXOXVMM2d0WjI5dloyeGxMV05vY205dFpTMXdaR1lpTENKemRXWm1hWGhsY3lJNkluQmtaaUo5WFgwc2V5SnVZVzFsSWpvaVEyaHliMjFwZFcwZ1VFUkdJRlpwWlhkbGNpSXNJbVJsYzJOeWFYQjBhVzl1SWpvaUlpd2liV2x0WlZSNWNHVnpJanBiZXlKMGVYQmxJam9pWVhCd2JHbGpZWFJwYjI0dmNHUm1JaXdpYzNWbVptbDRaWE1pT2lKd1pHWWlmVjE5WFE9PSxXeUo2YUMxRFRpSmQsMCwxLDAsMjQsMjM3NDE1OTMwLDgsMjI3MTI2NTIwLDAsMSwwLC00OTEyNzU1MjMsUjI5dloyeGxJRWx1WXk0Z1RtVjBjMk5oY0dVZ1IyVmphMjhnVjJsdU16SWdOUzR3SUNoWGFXNWtiM2R6SUU1VUlERXdMakE3SUZkcGJqWTBPeUI0TmpRcElFRndjR3hsVjJWaVMybDBMelV6Tnk0ek5pQW9TMGhVVFV3c0lHeHBhMlVnUjJWamEyOHBJRU5vY205dFpTOHhNamN1TUM0d0xqQWdVMkZtWVhKcEx6VXpOeTR6TmlBeU1EQXpNREV3TnlCTmIzcHBiR3hoLGV5SmphSEp2YldVaU9uc2lZWEJ3SWpwN0ltbHpTVzV6ZEdGc2JHVmtJanBtWVd4elpTd2lTVzV6ZEdGc2JGTjBZWFJsSWpwN0lrUkpVMEZDVEVWRUlqb2laR2x6WVdKc1pXUWlMQ0pKVGxOVVFVeE1SVVFpT2lKcGJuTjBZV3hzWldRaUxDSk9UMVJmU1U1VFZFRk1URVZFSWpvaWJtOTBYMmx1YzNSaGJHeGxaQ0o5TENKU2RXNXVhVzVuVTNSaGRHVWlPbnNpUTBGT1RrOVVYMUpWVGlJNkltTmhibTV2ZEY5eWRXNGlMQ0pTUlVGRVdWOVVUMTlTVlU0aU9pSnlaV0ZrZVY5MGIxOXlkVzRpTENKU1ZVNU9TVTVISWpvaWNuVnVibWx1WnlKOWZYMTksNjUsLTExODM0MTA3MiwxLDEsLTEsMTY5OTk1NDg4NywxNjk5OTU0ODg3LDMzNjAwNzkzMyw4; guest=true; x-hng=lang=zh-CN&domain=www.ozon.ru; __Secure-user-id=210183128; is_adult_confirmed=; is_alco_adult_confirmed=; ozonIdAuthResponseToken=eyJhbGciOiJIUzI1NiIsIm96b25pZCI6Im5vdHNlbnNpdGl2ZSIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoyMTAxODMxMjgsImlzX3JlZ2lzdHJhdGlvbiI6ZmFsc2UsInJldHVybl91cmwiOiIiLCJwYXlsb2FkIjpudWxsLCJleHAiOjE3NTI1NDczNzksImlhdCI6MTc1MjU0NzM2OSwiaXNzIjoib3pvbmlkIn0.DX0nfNf9rcuPdt9lzE5fn1en5yqiD7Aw1vqpRt13OiU; abt_data=7.iE3bYFZ2m7yMc8mn5Rm8V9pI_ELHBH8eHNcM1w0kxMd8-HXap37uTEk6E_nAsmdWsO5pYQKhwamysCHZexl_YPPpWOWk7wgfKSuP8pTEdlDlXwLOy-sokLKLOdHyTFxcxNx5yRfpmNqFoQP8D5KccoiDh5U5kU8x7rJDLpqixSah6TFKKYsTiZrokn5Tb5aJu5lMAZBOkhr7CkYTFd_4j9wtALKnFM-oZGxCX0qTgUP5kIf9MfDSGI0U0pZ6igW6aSGirFb5ZVNmCV2D4NImCGn00K_Sn8ZX0vR6krWW1cixLrCnKp0rO7JJEFi9c7-4FL54ZvaJ-tKg8ALwlPmRIr-aI156iSlHQU6lULo8oKmBL13eLI9d_d8fp50BJe7oA6PNylYhI5DdV81WGI2EFnZtR3sVYF8O4qrS7gXwsMCoku51prrYZG1pLJJiD8HfAPTnFLvxpURZ7x1-lsAinljoh8_N1oVP89PVPJjd-tKQyRiUnleAnwaeF9kQmGvIMqYSYi506QWf-KHqfdEnEA; __Secure-ETC=90ef0679199c55cdec8592a7e480c3c5; __Secure-access-token=8.210183128.eH-AgbboQkWnqX9a7XeDqQ.92.Aa5lCeHJOXOoA90AsaTtX6OsppswLMDuxLGNHW98_K79BKfyceiX8mpea_qxY5qoaYUOO7_5hsQ6ndRa9tCxUzOCvERd7f056ZaoOw8W9lnUgQn_q5TX-7X2WK8ejP7OZg.20250715024249.20250715073104.M4j1mEB0PD4T55SZ4iQqCG6lZytjYmwG50Q28F-3q_s.1a7857bd2a7e95575; __Secure-refresh-token=8.210183128.eH-AgbboQkWnqX9a7XeDqQ.92.Aa5lCeHJOXOoA90AsaTtX6OsppswLMDuxLGNHW98_K79BKfyceiX8mpea_qxY5qoaYUOO7_5hsQ6ndRa9tCxUzOCvERd7f056ZaoOw8W9lnUgQn_q5TX-7X2WK8ejP7OZg.20250715024249.20250715073104.Ip-CdBv1mEGilz15LZCTwFmfoxEL8RrayFNa90r_XLI.197047eb6a54a7041; is_cookies_accepted=1; token=eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjJiMDM5ODBiLWYzZDQtNDI2Ny04NTQ2LWYwZjQxOTczNTAyNiJ9.oIF-bHh7pubdNWzETutNcoc_Nu-A7zgqBIJwcHFgF0V2s-xfnZVbs_EbvyJSBYYkUjqrBlJP_1qBl3vB1mQ_ow");
Spider.create(new PageProcessor() {
@Override
public void process(Page page) {
List<String> imgs = page.getHtml()
.css("div.ip7_24.p7i_24 img.i7p_24.b95_3_1-a", "src").all();
page.putField("imageUrls", imgs);
}
@Override
public Site getSite() {
return site;
}
}).addUrl(url)
.addPipeline((resultItems, task) -> {
List<String> imgs = resultItems.get("imageUrls");
if (imgs != null) {
imageUrls.addAll(imgs);
}
})
.thread(1)
.run();
return AjaxResult.success(imageUrls);
}
}

View File

@ -0,0 +1,189 @@
package com.ruoyi.web.controller.tool;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.web.util.WebMagicProxyUtil;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;
import us.codecraft.webmagic.downloader.HttpClientDownloader;
import us.codecraft.webmagic.proxy.Proxy;
import us.codecraft.webmagic.proxy.SimpleProxyProvider;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.net.InetSocketAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 乐天市场控制器
*
* @author ruoyi
* @date 2025-07-15
*/
@RestController
@RequestMapping("/prod/rakuten")
public class RakutenController extends BaseController
{
private static final Logger logger = LoggerFactory.getLogger(RakutenController.class);
private final Random random = new Random();
private static final String DEFAULT_PROXY_HOST = "127.0.0.1";
private static final String DEFAULT_PROXY_PORT = "7890";
@GetMapping("/scrapeProducts")
public AjaxResult scrapeProducts()
{
String url = "https://ranking.rakuten.co.jp/search?stx=GBAmarket&smd=0&prl=&pru=&rvf=&arf=&vmd=0&ptn=1&srt=1&sgid=";
List<Map<String, String>> products = new ArrayList<>();
Site site = Site.me()
.setRetryTimes(3)
.setTimeOut(10000)
.setSleepTime(1000 + random.nextInt(2000));
site.addHeader("cookie", "_ra=1750472997398|0ff0eb32-5d9f-4c27-a7ca-c9ff1149e90b; Rp=779873a7b6c0e87edcf6f39cf2368561928309c8; rcx=136377ca-334f-4305-b45e-ad43e9538d2e; rcxGlobal=136377ca-334f-4305-b45e-ad43e9538d2e; _cc_id=5499b2bb8cabd8f467d3c872aaaffa96; panoramaId_expiry=1752628971187; panoramaId=541b9862d3ebc6afde47204c6b41a9fb927a29cfaf47c94f7efc477bd6e08018; panoramaIdType=panoDevice; x-hng=lang=zh-CN&domain=ranking.rakuten.co.jp; rat_v=b15ac773a5d64baf8dca92de5e06876098f84f76; Re=22.9.18.0.0.101839.1-22.9.18.0.0.101839.1; ak_bmsc=97272C637D70F43BF1F202AB32536B36~000000000000000000000000000000~YAAQGPAgF+Lm18yXAQAAsL0WDRzGbOBBlfAvGsV2IKdcTjZCiC3Uq+Y/Dmh6JHW6gn4q5X/8zVgCj7wEV/E+uAU4gX+3Yp/I1QvPmBxAOEgkawuEiktjCym641i25ZuBJ2CmpgqwGujbe7rDVR3pBCSzoR6AMAr4/JPfI8UjhcZ8QHDm/2RhnYM50NUBTz2Bb96oYmHmhiFxFrOrFbHbMJoEmXmOAXINh35/U6uR/q3iJG9psPoQMWrTHt36xGIzrD1WmEoOpDIgxGKtJdqWiI2HWYouhUrUm2LfJB3xqqKsDlEbzJKATdP1aZ8BaxZXLKgFuucr8fAYV2JXmQVlgAAAWMoi1BvgX31EVIo/HrbtXwqUWV+axZTgbRIaUzX3MUU2lLT9YqSrxE0U3RZeqnyTQLnNEtUhzzPb5ZtHJevwoHnihvv1/A2NrouNJDp2FVnORkSqjovEs9l/Z5CL/RI=; cto_bundle=Ho3NGF9iMU05dDg3TFBta25zOWUlMkJrbVVPSXRieXhvQkE2OHJwZ2Z2a3JoMElUak9LVEVYZHB5cXQ2N0pHTXpudHlHRXpodE9CUENETnRMdzhkQ1VjZU96UHhXVGpYcSUyQnZyd21ueDIwWTR2MWlPbGM3WG94ZHpqQW43R0NqViUyRjJ4NFEzNUN4OVFjRCUyQkFYVG1VTTdiQmMlMkZPY0dnJTNEJTNE; _fbp=fb.2.1752566251478.873208328842983622; __lt__cid.3df24f5b=f5a060ab-0930-48f9-9658-8d1e546fcc57; _gcl_au=1.1.1606205613.1752566252; _tt_enable_cookie=1; _ttp=01K06HDGJ7PVRWNX5APHE62MH7_.tt.2; ttcsid_COAFPAJC77U4F0RAECNG=1752566252155::HgNyOTPjyMcNbhYtz-AB.1.1752566252155; ttcsid=1752566252106::mH9Grdckk_3SH1Bp6UpS.1.1752566252155; _uetsid=5cd4d200615111f0adebf3cbc093b02c; _uetvid=5cd50bd0615111f099aad7893516afe1; ttcsid_COAECTBC77U6F5DVOFS0=1752566252105::NC3L-b3aoP3YEuOGO4Qy.1.1752566253162; __gads=ID=8405c2d7614905e4:T=1752542570:RT=1752567475:S=ALNI_MaVrnkQdqffpLsBqCP5rcOOwIHewA; __gpi=UID=00001161dccd98cd:T=1752542570:RT=1752567475:S=ALNI_MZJ2ECy0VeTmUs9-7zNFppf4p7hOA; __eoi=ID=203c5e82c3cfee8a:T=1752542570:RT=1752567475:S=AA-AfjbN1_VcxivizXN3BbwEd-hI; FCNEC=%5B%5B%22AKsRol-HQYh5qu3EpcFJpGA81sC9pMn6YPm8F2rJtAybtg_7Cxn5XyIO04J9m_6HYBryz3D_sQi4P7sqtyVUhgj5ErkOXscKPBp34F1WjlPK6qU1uMHdxkKlXh57F7w4PMg2NVNniULz_4s12zYbVlemYykDcysxIA%3D%3D%22%5D%5D; Rb=1s80a73M01&omnir34; Rz=A8_cJHchUvQYPegyHwUBQ1PclSzsfP0rVH_Yt2voU0RIPEm8XdGVcjZetzEPlSpPcGr2h32POT39MAYXZcpjwoMSYDiGKJSIKsyWKl1d3Ley; Rq=M01.1&687617b7; Ra=A78vLV4ixp9ENQ1IvxXsZGSpfP-4acHSGn2AUu4KQz2jP7zLjLOPOjOkPdMeqbFaieOrKh3nQNfqaalxrq86sdubCJcxt26r3k-IdgcqiMMowQ~~");
site.addHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36");
site.addHeader("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7");
site.addHeader("accept-encoding", "gzip, deflate, br, zstd");
site.addHeader("accept-language", "zh-CN,zh;q=0.9");
site.addHeader("cache-control", "max-age=0");
site.addHeader("priority", "u=0, i");
site.addHeader("referer", "https://login.account.rakuten.com/");
site.addHeader("sec-ch-ua", "\"Not)A;Brand\";v=\"99\", \"Microsoft Edge\";v=\"127\", \"Chromium\";v=\"127\"");
site.addHeader("sec-ch-ua-mobile", "?0");
site.addHeader("sec-ch-ua-platform", "\"Windows\"");
site.addHeader("sec-fetch-dest", "document");
site.addHeader("sec-fetch-mode", "navigate");
site.addHeader("sec-fetch-site", "cross-site");
site.addHeader("sec-fetch-user", "?1");
site.addHeader("upgrade-insecure-requests", "1");
System.setProperty("http.proxyHost", DEFAULT_PROXY_HOST);
System.setProperty("http.proxyPort", DEFAULT_PROXY_PORT);
System.setProperty("https.proxyHost", DEFAULT_PROXY_HOST);
System.setProperty("https.proxyPort", DEFAULT_PROXY_PORT);
HttpClientDownloader httpClientDownloader = new HttpClientDownloader();
httpClientDownloader.setProxyProvider(SimpleProxyProvider.from(
new Proxy(DEFAULT_PROXY_HOST, Integer.parseInt(DEFAULT_PROXY_PORT))
));
Spider.create(new PageProcessor() {
@Override
public void process(Page page) {
// 使用指定的HTML结构提取所有店铺信息
List<String> shopElements = page.getHtml().css("div.srhPic div.rnkRanking_bigImageBox").all();
for (String element : shopElements) {
Map<String, String> product = new HashMap<>();
String productUrl = page.getHtml().xpath(element + "/a/@href").toString();
if (productUrl != null) {
String[] parts = productUrl.split("/");
if (parts.length > 3) {
product.put("shopName", parts[3]); // 从URL提取店铺名称
}
}
String imageUrl = page.getHtml().xpath(element + "/a/img/@src").toString();
if (imageUrl != null) {
product.put("imageUrl", imageUrl);
}
if (productUrl != null) {
product.put("productUrl", productUrl);
}
if (!product.isEmpty()) {
products.add(product);
}
}
if (products.isEmpty()) {
List<String> urls = page.getHtml().xpath("//div[@class='srhPic']//div[@class='rnkRanking_bigImageBox']/a/@href").all();
List<String> images = page.getHtml().xpath("//div[@class='srhPic']//div[@class='rnkRanking_bigImageBox']/a/img/@src").all();
for (int i = 0; i < Math.min(urls.size(), images.size()); i++) {
Map<String, String> product = new HashMap<>();
String productUrl = urls.get(i);
String imageUrl = images.get(i);
String[] parts = productUrl.split("/");
if (parts.length > 3) {
product.put("shopName", parts[3]);
}
product.put("imageUrl", imageUrl);
product.put("productUrl", productUrl);
products.add(product);
}
}
if (products.isEmpty()) {
logger.info("未找到商品数据,页面内容: {}", page.getHtml().toString());
logger.info("HTTP状态码: {}", page.getStatusCode());
logger.info("请求URL: {}", page.getUrl().toString());
}
}
@Override
public Site getSite() {
return site;
}
})
.addUrl(url)
.setDownloader(httpClientDownloader)
.thread(1)
.run();
clearSystemProxy();
return AjaxResult.success(products);
}
/**
* 获取所有可用代理节点
*/
@GetMapping("/proxies")
public AjaxResult getProxies() {
List<Map<String, String>> proxies = new ArrayList<>();
Map<String, String> defaultProxy = new HashMap<>();
defaultProxy.put("name", "默认代理");
defaultProxy.put("server", DEFAULT_PROXY_HOST);
defaultProxy.put("port", DEFAULT_PROXY_PORT);
defaultProxy.put("type", "http");
proxies.add(defaultProxy);
return AjaxResult.success(proxies);
}
/**
* 清除系统代理设置
*/
private void clearSystemProxy() {
System.clearProperty("http.proxyHost");
System.clearProperty("http.proxyPort");
System.clearProperty("https.proxyHost");
System.clearProperty("https.proxyPort");
System.clearProperty("socksProxyHost");
System.clearProperty("socksProxyPort");
}
}

View File

@ -398,7 +398,7 @@ public class WebMagicController extends BaseController implements PageProcessor
String price = (String) resultMap.get("price");
String resultAsin = (String) resultMap.get("asin");
if (resultAsin == null || resultAsin.isEmpty() || price == null || price.isEmpty()) {
logger.info("ASIN: {} 首次爬取结果不完整,等待后重试", asin);
Thread.sleep(1000 + random.nextInt(20000));
@ -451,7 +451,6 @@ public class WebMagicController extends BaseController implements PageProcessor
break;
}
int waitSeconds = 30 + random.nextInt(21);
logger.info("第{}次重试,等待{}秒...", retryRound + 1, waitSeconds);
Thread.sleep(waitSeconds * 1000);
List<String> currentFailedAsins = new ArrayList<>(failedAsins);
@ -466,8 +465,6 @@ public class WebMagicController extends BaseController implements PageProcessor
.addUrl(url)
.setDownloader(getProxyDownloader())
.thread(1);
// 记录当前活动的爬虫
synchronized (spiderLock) {
activeSpider = spider;
}
@ -501,20 +498,11 @@ public class WebMagicController extends BaseController implements PageProcessor
}
logger.info("第{}次重试完成,还有{}个ASIN未成功获取数据", retryRound + 1, failedAsins.size());
}
if (failedAsins.isEmpty()) {
logger.info("所有ASIN均成功获取数据");
} else {
logger.warn("经过3次重试仍有{}个ASIN未能成功获取数据", failedAsins.size());
}
}
// 清除爬虫引用
synchronized (spiderLock) {
activeSpider = null;
}
logger.info("批量爬取完成,结果数量: {}", resultList.size());
deferredResult.setResult(ResponseEntity.ok(R.ok(resultList)));
} catch (Exception e) {
clearSystemProxy();

View File

@ -0,0 +1,9 @@
import request from '@/utils/request'
// 爬取图片
export function scrapeImages() {
return request({
url: '/prod/ozon/scrapeImages',
method: 'get'
})
}

View File

@ -0,0 +1,9 @@
import request from '@/utils/request'
// 从乐天市场抓取商品数据
export function scrapeRakutenProducts() {
return request({
url: '/prod/rakuten/scrapeProducts',
method: 'get'
})
}

View File

@ -0,0 +1,85 @@
<template>
<div class="app-container">
<el-card class="box-card">
<template #header>
<div class="clearfix">
<span class="card-title">图片爬取</span>
</div>
</template>
<el-row>
<el-col :span="24">
<el-button
type="primary"
icon="el-icon-search"
@click="handleScrape"
:loading="loading"
>开始爬取</el-button>
</el-col>
</el-row>
<el-row v-if="imageUrls.length > 0" class="image-container">
<el-col :xs="8" :sm="6" :md="4" :lg="4" :xl="3" v-for="(url, index) in imageUrls" :key="index">
<el-image :src="url" :preview-src-list="[url]" fit="cover" class="image-item" />
</el-col>
</el-row>
<el-empty v-else description="暂无图片数据" />
</el-card>
</div>
</template>
<script>
import { scrapeImages } from "@/api/prod/ozon";
export default {
name: "Ozon",
data() {
return {
loading: false,
imageUrls: []
};
},
methods: {
handleScrape() {
this.loading = true;
this.imageUrls = [];
scrapeImages().then(response => {
if (response.code === 200) {
this.imageUrls = response.data || [];
if (this.imageUrls.length === 0) {
this.$message.warning("未找到图片");
}
} else {
this.$message.error(response.msg || "爬取失败");
}
this.loading = false;
}).catch(() => {
this.$message.error("爬取请求失败");
this.loading = false;
});
}
}
};
</script>
<style scoped>
.card-title {
font-size: 16px;
font-weight: bold;
}
.image-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 20px;
}
.image-item {
width: 100%;
height: 200px;
border: 1px solid #eee;
border-radius: 4px;
margin-bottom: 10px;
}
</style>

View File

@ -0,0 +1,110 @@
<template>
<div class="app-container">
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-download"
size="mini"
@click="handleScrape"
>获取最新数据</el-button>
</el-col>
<el-col :span="5">
<span class="proxy-info">使用默认代理: 127.0.0.1:7890</span>
</el-col>
</el-row>
<el-card v-loading="loading">
<div v-if="products.length === 0 && !loading" class="empty-data">
<span>点击"获取最新数据"按钮抓取商品信息</span>
</div>
<el-row :gutter="20" v-else>
<el-col :span="6" v-for="(item, index) in products" :key="index">
<el-card :body-style="{ padding: '10px' }" shadow="hover" class="product-card">
<div class="shop-name">{{ item.shopName }}</div>
<div class="image-container">
<a :href="item.productUrl" target="_blank">
<img :src="item.imageUrl" class="product-image" />
</a>
</div>
</el-card>
</el-col>
</el-row>
</el-card>
</div>
</template>
<script>
import { scrapeRakutenProducts } from "@/api/prod/rakuten"
export default {
name: "Rakuten",
data() {
return {
//
loading: false,
//
products: []
}
},
methods: {
/** 抓取乐天市场商品数据 */
handleScrape() {
this.loading = true
scrapeRakutenProducts().then(response => {
this.products = response.data || []
this.loading = false
if (this.products.length > 0) {
this.$message.success(`成功获取${this.products.length}个商品信息`)
} else {
this.$message.warning('未找到商品信息')
}
}).catch(() => {
this.loading = false
this.$message.error('获取商品信息失败')
})
}
}
}
</script>
<style scoped>
.product-card {
margin-bottom: 20px;
height: 220px;
display: flex;
flex-direction: column;
}
.shop-name {
height: 40px;
overflow: hidden;
text-overflow: ellipsis;
font-weight: bold;
margin-bottom: 10px;
}
.image-container {
text-align: center;
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
}
.product-image {
max-width: 100%;
max-height: 150px;
object-fit: contain;
}
.empty-data {
text-align: center;
padding: 40px;
color: #909399;
font-size: 14px;
}
.proxy-info {
color: #909399;
font-size: 13px;
line-height: 32px;
}
</style>