// ===== SEARCH PLUS v8: Real web scraping + LLM hybrid =====
let searchPlusOpen=false;
window._spLastResults=[];
const CORS_PROXIES=[
q=>'https://api.allorigins.win/get?url='+encodeURIComponent(q),
q=>'https://api.codetabs.com/v1/proxy?quest='+encodeURIComponent(q)
];
function toggleSearchPlus(){
let o=document.getElementById('searchPlusOverlay');
if(!o)createSearchPlusUI();
o=document.getElementById('searchPlusOverlay');
searchPlusOpen=!searchPlusOpen;
o.classList.toggle('open',searchPlusOpen);
if(searchPlusOpen)document.getElementById('spQuery').focus();
}
function createSearchPlusUI(){
let d=document.createElement('div');
d.innerHTML=`
🔍✨
Tìm Kiếm Plus — Web Scraper
Tìm sản phẩm thực tế từ web · Hình ảnh & giá chính xác
🌐 Google Shopping🏪 dienmayxanh.com🏪 thegioibepnhapkhau.vn🏪 hafele.com.vn
🔍✨
Nhập tên sản phẩm để tìm từ các website bên ngoài
Dữ liệu scrape trực tiếp — hình ảnh & giá thật
`;
document.body.appendChild(d);
let s=document.createElement('style');
s.textContent='#searchPlusOverlay.open{display:flex!important}';
document.head.appendChild(s);
}
async function runSearchPlus(){
let query=document.getElementById('spQuery').value.trim();
if(!query)return;
let results=document.getElementById('spResults');
let btn=document.getElementById('spSearchBtn');
btn.disabled=true;btn.innerHTML=' Đang tìm...';
results.innerHTML=`
Đang scrape dữ liệu từ web...
`;
let local=localSearch(query).filter(p=>!p._notfound);
let external=[];
try{
updateProgress('🔍 Đang tìm trên Google...');
let googleResults=await scrapeGoogle(query);
if(googleResults.length) external.push(...googleResults);
}catch(e){console.warn('Google scrape failed:',e)}
try{
updateProgress('🏪 Đang scrape thegioibepnhapkhau.vn...');
let tgbResults=await scrapeTGBNK(query);
if(tgbResults.length) external.push(...tgbResults);
}catch(e){console.warn('TGBNK scrape failed:',e)}
if(external.length<3){
let vars=window.huggingface?.variables||{};
let token=vars.HF_TOKEN||vars.VAISTUDIO||vars.AI_TOKEN||vars.TOKEN||'';
if(token&&token.startsWith('hf_')){
try{
updateProgress('🤖 Bổ sung thông tin từ AI...');
let aiResults=await searchFromAI(query,token);
if(aiResults.length) external.push(...aiResults);
}catch(e){console.warn('AI search failed:',e)}
}
}
let all=[...local];
let seenNames=new Set(local.map(p=>(p.name||'').toLowerCase().replace(/[^a-z0-9]/g,'')));
external.forEach(p=>{
let key=(p.name||'').toLowerCase().replace(/[^a-z0-9]/g,'');
if(key.length>5 && !seenNames.has(key)){
all.push(p);
seenNames.add(key);
}
});
if(!all.length)all=[{_notfound:true}];
window._spLastResults=all;
results.innerHTML=renderSPResults(all);
btn.disabled=false;btn.innerHTML='🔍 Tìm';
}
function updateProgress(msg){
let el=document.getElementById('spProgress');
if(el)el.textContent=msg;
}
async function scrapeGoogle(query){
let searchUrl='https://www.google.com/search?q='+encodeURIComponent(query+' giá site:dienmayxanh.com OR site:thegioibepnhapkhau.vn OR site:hafele.com.vn OR site:hmh.com.vn')+'&hl=vi&num=8';
let proxyUrl=CORS_PROXIES[0](searchUrl);
let resp=await fetch(proxyUrl,{signal:AbortSignal.timeout(12000)});
if(!resp.ok) return [];
let data=await resp.json();
let html=data.contents||'';
if(!html||html.length<500) return [];
let products=[];
let parser=new DOMParser();
let doc=parser.parseFromString(html,'text/html');
let resultDivs=doc.querySelectorAll('.g, div[data-hveid]');
resultDivs.forEach(div=>{
let linkEl=div.querySelector('a[href^="http"]');
let titleEl=div.querySelector('h3');
let snippetEl=div.querySelector('.VwiC3b, [data-sncf], .lEBKkf');
if(!linkEl||!titleEl) return;
let url=linkEl.href||'';
let title=titleEl.textContent||'';
let snippet=(snippetEl?snippetEl.textContent:'').trim();
if(!/(dienmayxanh|thegioibepnhapkhau|hafele|hmh)/.test(url)) return;
let priceMatch=snippet.match(/(\d{1,3}(?:\.\d{3})+)\s*(?:₫|đ|VNĐ|vnđ)/i)||
snippet.match(/(\d{1,3}(?:\.\d{3})+)\s*(?:đồng)/i)||
title.match(/(\d{1,3}(?:\.\d{3})+)\s*(?:₫|đ)/i);
let price=priceMatch?priceMatch[1]+'đ':'';
let priceNum=priceMatch?parseInt(priceMatch[1].replace(/\./g,'')):0;
let source='Web';
if(url.includes('dienmayxanh')) source='Điện Máy Xanh';
else if(url.includes('thegioibepnhapkhau')) source='Thế Giới Bếp NK';
else if(url.includes('hafele')) source='Hafele VN';
else if(url.includes('hmh')) source='HMH';
let brand='';
let sku='';
['Malloca','Eurogold','Grob','Canzy','Demax','Hafele','Bosch','Teka','Electrolux','Faster','Kaff'].forEach(b=>{
if(title.toLowerCase().includes(b.toLowerCase())) brand=b;
});
let skuMatch=title.match(/\b([A-Z]{1,5}[-\s]?[A-Z0-9]{2,}(?:[-][A-Z0-9]+)*)\b/);
if(skuMatch) sku=skuMatch[1];
products.push({
name:title.replace(/\s*[-|–]\s*(Điện Máy Xanh|Thế Giới Bếp|HAFELE|hmh).*$/i,'').trim(),
sku:sku,brand:brand,price:price,priceNum:priceNum,
category:'Thiết bị bếp',description:snippet,
specs:{},features:[],image:'',
source:source,sourceUrl:url,_needImage:true
});
});
await Promise.allSettled(products.slice(0,6).map(async(p,i)=>{
if(p.sourceUrl){
try{
let img=await scrapeProductImage(p.sourceUrl,p.name);
if(img) products[i].image=img;
}catch(e){}
}
if(!products[i].image){
products[i].image=getGoogleImageUrl(p.name+(p.sku?' '+p.sku:''));
}
}));
return products;
}
async function scrapeTGBNK(query){
let searchUrl='https://www.thegioibepnhapkhau.vn/?s='+encodeURIComponent(query);
let proxyUrl=CORS_PROXIES[0](searchUrl);
let resp=await fetch(proxyUrl,{signal:AbortSignal.timeout(12000)});
if(!resp.ok) return [];
let data=await resp.json();
let html=data.contents||'';
if(!html||html.length<1000) return [];
let products=[];
let parser=new DOMParser();
let doc=parser.parseFromString(html,'text/html');
let linkEls=doc.querySelectorAll('a[href*="/san-pham/"], a[href*="/product/"], a[href*="/bep-"], a[href*="/may-"], a[href*="/lo-"], a[href*="/khoa-"]');
let seen=new Set();
linkEls.forEach(link=>{
let href=link.href||link.getAttribute('href')||'';
if(!href||href==='#'||seen.has(href)) return;
let name=link.textContent.trim()||link.title||'';
if(name.length<5||name.length>200) return;
let parent=link.closest('.product-item, .product-card, .item, article, .product, .col-md-3, .col-sm-6, .col-xs-12');
let img=parent?parent.querySelector('img'):null;
let imgSrc='';
if(img){
imgSrc=img.getAttribute('data-src')||img.getAttribute('src')||'';
}
let priceEl=parent?parent.querySelector('.price, [class*="price"], .gia, [class*="gia"]'):null;
let priceText=priceEl?priceEl.textContent:'';
let priceMatch=priceText.match(/(\d{1,3}(?:\.\d{3})+)/);
if(href.startsWith('/')) href='https://www.thegioibepnhapkhau.vn'+href;
let brand='';
['Malloca','Eurogold','Grob','Canzy','Demax','Hafele','Bosch','Teka'].forEach(b=>{
if(name.toLowerCase().includes(b.toLowerCase())) brand=b;
});
seen.add(href);
products.push({
name:name.replace(/<[^>]*>/g,'').trim(),
sku:'',brand:brand,
price:priceMatch?priceMatch[1]+'đ':'Liên hệ',
priceNum:priceMatch?parseInt(priceMatch[1].replace(/\./g,'')):0,
category:'Thiết bị bếp',description:'',
specs:{},features:[],image:imgSrc||'',
source:'Thế Giới Bếp NK',sourceUrl:href
});
});
return products.slice(0,6);
}
async function scrapeProductImage(url,productName){
try{
let proxyUrl=CORS_PROXIES[0](url);
let resp=await fetch(proxyUrl,{signal:AbortSignal.timeout(8000)});
if(!resp.ok) return '';
let data=await resp.json();
let html=data.contents||'';
if(!html) return '';
let parser=new DOMParser();
let doc=parser.parseFromString(html,'text/html');
let ldScripts=doc.querySelectorAll('script[type="application/ld+json"]');
for(let script of ldScripts){
try{
let ld=JSON.parse(script.textContent);
if(Array.isArray(ld)) ld=ld.find(x=>x['@type']==='Product')||ld[0];
if(ld['@type']==='Product'){
let img=ld.image;
if(Array.isArray(img)) img=img[0];
if(typeof img==='object') img=img.url||img.contentUrl;
if(img&&img.startsWith('http')) return img;
}
}catch(e){}
}
let ogImg=doc.querySelector('meta[property="og:image"]');
if(ogImg){
let src=ogImg.getAttribute('content')||'';
if(src&&src.startsWith('http')&&!src.includes('logo')&&!src.includes('favicon')) return src;
}
let mainImg=doc.querySelector('.product-image img, .gallery-main img, .product-detail img, [class*="product"] img[src*="product"], .product__image img');
if(mainImg){
let src=mainImg.getAttribute('data-src')||mainImg.getAttribute('src')||'';
if(src&&src.startsWith('http')&&src.match(/\.(jpg|png|webp)/i)) return src;
}
let allImgs=doc.querySelectorAll('img');
for(let img of allImgs){
let src=img.getAttribute('data-src')||img.getAttribute('src')||'';
let w=parseInt(img.getAttribute('width')||'0');
let h=parseInt(img.getAttribute('height')||'0');
if(src&&src.startsWith('http')&&(w>200||h>200||src.includes('product'))&&!src.includes('logo')&&!src.includes('icon')&&!src.includes('banner')){
return src;
}
}
}catch(e){}
return '';
}
function getGoogleImageUrl(query){
return '';
}
async function searchFromAI(query,token){
let resp=await fetch('https://router.huggingface.co/v1/chat/completions',{
method:'POST',
headers:{'Authorization':'Bearer '+token,'Content-Type':'application/json'},
signal:AbortSignal.timeout(20000),
body:JSON.stringify({
model:'meta-llama/Llama-3.3-70B-Instruct',
messages:[{role:'system',content:`Bạn là hệ thống tra cứu sản phẩm thiết bị nhà bếp & phụ kiện tại Việt Nam.
CHỈ trả về sản phẩm bạn CHẮC CHẮN tồn tại thực tế. Không bịa sản phẩm.
MỖI sản phẩm cần:
- sku: mã SP chính hãng (phải chính xác, VD: MH02I, EUH2288PRO...)
- name: tên đầy đủ
- brand: thương hiệu
- price: giá niêm yết VNĐ (ước lượng dựa trên phân khúc nếu không biết chính xác)
- specs: object với các thông số THỰC (kích thước, công suất, chất liệu, xuất xứ, bảo hành)
- features: array 3-4 tính năng nổi bật
- description: mô tả 2-3 câu
- source: "AI tổng hợp"
KHÔNG bao gồm trường image (sẽ tự xử lý).
Trả về JSON array. KHÔNG có text khác.`},
{role:'user',content:`Tìm: "${query}" — trả về 4-6 sản phẩm thực tế.`}],
max_tokens:2500,temperature:0.1
})
});
if(!resp.ok) return [];
let data=await resp.json();
let text=data.choices?.[0]?.message?.content||'';
let m=text.match(/\[[\s\S]*?\]/);
if(!m) return [];
try{
let products=JSON.parse(m[0]);
return products.filter(p=>p.name).map(p=>({
sku:p.sku||'',name:p.name||'',brand:p.brand||'',
price:p.price||(p.priceNum>0?p.priceNum.toLocaleString('vi-VN')+'đ':''),
priceNum:p.priceNum||parseInt(String(p.price||'').replace(/[^\d]/g,''))||0,
category:p.category||'Thiết bị bếp',
specs:p.specs||{},features:p.features||[],
image:'',description:p.description||'',
source:p.source||'AI tổng hợp',sourceUrl:'',_fromAI:true
}));
}catch(e){return []}
}
function localSearch(query){
let q=query.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d');
if(typeof D!=='undefined'&&D.length){
let matched=D.filter(p=>{
let idx=(p.name+' '+p.model+' '+p.brand+' '+p.cat+' '+(p.sku||'')).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'').replace(/đ/g,'d');
return q.split(/\s+/).filter(w=>w.length>1).every(w=>idx.includes(w));
}).slice(0,3);
if(matched.length)return matched.map(p=>({
sku:p.sku||p.model,name:p.name,brand:p.brand,
price:p.price||'Liên hệ',priceNum:p.priceNum||0,
category:p.cat,specs:p.specs||{},features:p.feats||[],
image:p.image,description:p.summary||'',
source:'V.AI STUDIO',sourceUrl:'',_existing:true
}));
}
return[{_notfound:true}];
}
function renderSPResults(products){
let real=products.filter(p=>!p._notfound);
if(!real.length)return`
😕
Không tìm thấy sản phẩm.
Thử từ khóa khác hoặc mã sản phẩm cụ thể hơn.
`;
let scrapeCount=real.filter(p=>!p._existing&&!p._fromAI).length;
let aiCount=real.filter(p=>p._fromAI).length;
let localCount=real.filter(p=>p._existing).length;
let summary=`
${real.length} sản phẩm
${localCount?'✅ '+localCount+' trong kho':''}
${scrapeCount?'🌐 '+scrapeCount+' từ web':''}
${aiCount?'🤖 '+aiCount+' AI tổng hợp':''}
`;
return summary+real.map((p,i)=>{
let img=makeImg(p,90);
let priceHtml=p.price&&p.price!=='Liên hệ'?'
'+p.price+'
':'
Liên hệ báo giá
';
let specs='';
if(p.specs&&Object.keys(p.specs).length)
specs='
';
let src='';
if(!p._existing){
if(p.sourceUrl) src=''+p.source+' ↗';
else if(p._fromAI) src='🤖 '+p.source+'';
else src='📍'+p.source+'';
}
let verifyLink=p.sourceUrl?'':(!p._existing?' Verify':'');
let btn=p._existing?'✅ Đã có':'';
return`
${img}
${p.sku?'
'+p.sku+(p.brand?' · '+p.brand:'')+'
':''}
${p.name}
${priceHtml}${p.description?'
'+p.description+'
':''}${specs}
${btn}${src}${verifyLink}
`;
}).join('');
}
function makeImg(p,sz){
if(p.image&&p.image.startsWith('http'))
return`
`;
return phDiv(p,sz);
}
function phDiv(p,sz){
let c=(p.brand||p.name||'?').charAt(0).toUpperCase();
let isWeb=p.sourceUrl&&!p._existing;
let bg=isWeb?'linear-gradient(135deg,#059669,#10b981)':'linear-gradient(135deg,#003f62,#0077b6)';
return`
${c}
${(p.brand||'').substring(0,10)}
${isWeb?'
🌐 web
':''}
`;
}
function esc(s){return s.replace(/'/g,"\\'")}
window.phImg=function(b,src){return`
${src?'🌐':'📦'}
${(b||'').substring(0,8)}
`};
function previewSP(idx){
let p=window._spLastResults.filter(x=>!x._notfound)[idx];
if(!p||p._existing)return;
let img=p.image&&p.image.startsWith('http')?`
`:`
${(p.brand||'?').charAt(0)}
${p._fromAI?'🤖 AI — Ảnh chưa xác minh':'🌐 Ảnh đang cập nhật'}
`;
let specs=p.specs&&Object.keys(p.specs).length?`
Nguồn: AI tổng hợp — Nên xác minh thông tin trước khi sử dụng
`;
let verifyBtn=` Google ảnh`;
let ov=document.createElement('div');ov.id='spPrev';
ov.style.cssText='position:fixed;inset:0;background:rgba(0,0,0,.6);z-index:260;display:flex;align-items:center;justify-content:center;padding:16px;backdrop-filter:blur(4px)';
ov.onclick=function(e){if(e.target===ov)ov.remove()};
ov.innerHTML=`