'use client'; import { useState, useEffect } from 'react'; export default function CrawlNewsButton() { const [isCrawling, setIsCrawling] = useState(false); const [result, setResult] = useState(null); const [showResult, setShowResult] = useState(false); const [progress, setProgress] = useState({ current: 0, total: 0 }); // 크롤링 중 페이지 이탈 방지 경고 useEffect(() => { const handleBeforeUnload = (e) => { if (isCrawling) { e.preventDefault(); e.returnValue = "뉴스 수집이 진행 중입니다. 페이지를 떠나도 작업은 백그라운드에서 계속됩니다."; return e.returnValue; } }; if (isCrawling) { window.addEventListener("beforeunload", handleBeforeUnload); } return () => { window.removeEventListener("beforeunload", handleBeforeUnload); }; }, [isCrawling]); const handleCrawl = async () => { setIsCrawling(true); setResult({ newItems: 0, total: 0, sources: {} }); setShowResult(true); // 즉시 결과창 표시 const sources = [ 'vnexpress', 'vnexpress-vn', 'vnexpress-economy', 'vnexpress-realestate', 'vnexpress-travel', 'vnexpress-health', 'cafef', 'cafef-realestate', 'yonhap', 'insidevina', 'tuoitre', 'thanhnien', 'saigoneer', 'soranews24', 'thedodo', 'petmd', 'bonappetit', 'health' ]; const results = {}; let totalItems = 0; setProgress({ current: 0, total: sources.length }); // 18개 소스를 순차 크롤링 - 에러 나도 절대 멈추지 않음! for (let i = 0; i < sources.length; i++) { const source = sources[i]; const sourceName = sourceLabels[capitalizeSource(source)] || source; try { setProgress({ current: i + 1, total: sources.length }); // 크롤링 중 표시 results[sourceName] = '⏳'; setResult({ newItems: totalItems, total: totalItems, sources: { ...results } }); try { const response = await fetch('/api/crawl-source', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ source }), keepalive: true }); if (response.ok) { const data = await response.json(); const count = data.count || 0; results[sourceName] = count; totalItems += count; } else { results[sourceName] = '❌ 실패'; } } catch (err) { console.error(`[${source}] Fetch error:`, err); results[sourceName] = '❌ 에러'; } // 실시간 업데이트 setResult({ newItems: totalItems, total: totalItems, sources: { ...results } }); } catch (err) { // 만약의 경우를 대비한 이중 안전장치 console.error(`[${source}] Unexpected error:`, err); results[sourceName] = '❌ 예외'; setResult({ newItems: totalItems, total: totalItems, sources: { ...results } }); } } // 모든 크롤링 완료 setIsCrawling(false); }; // Helper function const capitalizeSource = (source) => { const mapping = { 'vnexpress': 'VnExpress', 'vnexpress-vn': 'VnExpress VN', 'vnexpress-economy': 'VnExpress Economy', 'vnexpress-realestate': 'VnExpress Real Estate', 'vnexpress-travel': 'VnExpress Travel', 'vnexpress-health': 'VnExpress Health', 'cafef': 'Cafef', 'cafef-realestate': 'Cafef Real Estate', 'yonhap': 'Yonhap News', 'insidevina': 'InsideVina', 'tuoitre': 'TuoiTre', 'thanhnien': 'ThanhNien', 'saigoneer': 'Saigoneer', 'soranews24': 'SoraNews24', 'thedodo': 'The Dodo', 'petmd': 'PetMD', 'bonappetit': 'Bon Appétit', 'health': 'Health' }; return mapping[source] || source; }; const handleClose = () => { setShowResult(false); window.location.reload(); }; const sourceLabels = { 'VnExpress': 'VnExpress (영문)', 'VnExpress VN': 'VnExpress (베트남어)', 'VnExpress Economy': 'VnExpress Economy (경제)', 'VnExpress Real Estate': 'VnExpress Real Estate (부동산)', 'VnExpress Travel': 'VnExpress Travel (여행)', 'VnExpress Health': 'VnExpress Health (건강)', 'Cafef': 'Cafef (경제 전문)', 'Cafef Real Estate': 'Cafef Real Estate (부동산)', 'Yonhap News': 'Yonhap (연합뉴스)', 'InsideVina': 'InsideVina', 'TuoiTre': 'TuoiTre (Tuổi Trẻ)', 'ThanhNien': 'ThanhNien (Thanh Niên)', 'Saigoneer': 'Saigoneer (음식/여행)', 'SoraNews24': 'SoraNews24 (펫/여행)', 'The Dodo': 'The Dodo (펫)', 'PetMD': 'PetMD (펫)', 'Bon Appétit': 'Bon Appétit (음식/레시피)', 'Health': 'Health (건강/웰니스)' }; return ( <> {showResult && result && (

🎉 뉴스 수집 완료!

{result.newItems}개
새 뉴스 저장됨
{result.total > result.newItems && (
(총 {result.total}개 중 {result.total - result.newItems}개 중복 제외)
)}
{result.sources && Object.entries(result.sources).map(([source, count]) => ( ))}
소스 수집 상태
{source} {typeof count === 'number' ? `${count}개` : count} {count === '⏳' ? ( ) : typeof count === 'number' && count > 0 ? ( ) : typeof count === 'string' && count.includes('❌') ? ( ) : ( - )}
)} ); } 청소 도우미 구하기 - Xin Chao Vietnam

청소 도우미 구하기

한 번도 안 한 사람은 있어도 한 번만 한 사람은 없다는 청소도우미 서비스. 삶의 질을 수직상승 시켜준다 해도 과언이 아니다. 요즘 한국에서는 청소 도우미 서비스를 어플로 간단 예약할 수 있을 정도로 많은 사람들이 애용하고 있는 서비스이다. 한국에서는 3시간에 4만원부터 시작인 반면, 하노이는 2시간에 5천원정도로 가격이 비교도 안 될 정도로 저렴하다. 인건비가 낮은 베트남에서 거주한다면 반드시 활용해야하는 서비스 중 하나다.
장기 출장 와서 직접 빨래, 청소까지 신경쓰기 어려울 때, 아이가 있어 청소할 시간이 없을 때, 강아지 산책이 필요할 때, 요리, 다림질, 심지어 가끔 집 전체 대청소가 필요할 때 청소 도우미 서비스를 이용한다면 굉장히 편한 하노이 생활을 할 수 있을 것이다.

청소도우미 어플은 bTaskee, Jupviec.vn등이 있으며 bTaskee는 어플 내 한글 지원이 되기 때문에 다른 어플보다 편리하게 이용할 수 있다.
만약 당신이 빈홈 등 규모가 있는 아파트 단지에 거주한다면 리셉션에 클린룸 서비스를 요청할 수 있다. 신원이 확실한 도우미에게 정기적으로 청소서비스를 받을 수 있으나 가격이 조금 높다.

광고

청소도우미 비용
집 규모, 청소 종류에 따라 차등이 있으나 예를 들어 룸2 아파트 청소일 경우 1시간 5만동이다. 도우미가 한국어를 한다거나 요리 등 다양한 서비스를 추가하면 비용이 조금 늘어난다. 한국에 다녀 온 경험이 있어 약간의 말이 통한다 거나 간단한 찌개 정도를 만들 줄 아는 도우미는 대부분 일정이 꽉 차있다. 아이가 많은 집이나 맞벌이의 경우 가장 선호하는 도우미이기에 귀국 등의 이유로 시간여유가 생기지 않으면 만나기도 어렵다. 대청소도 가능한데 도우미 2명 4시간 대청소 시 70만동이면 천장부터 바닥까지, 유리창과 선풍기 날개까지 구석구석 대청소가 가능하다.
청소 도우미 팁
• 타국에서 누군지 모르는 사람을 집에 들이는 건 어느정도 위험부담이 있다. 집안의 물건이 없어지거나 돈이 사라진 경우가 종종 발생하므로 청소도우미와 신뢰가 쌓이지 않았을 경우 자리를 비우면 안된다. 식탁에 50만동짜리 지폐가 여러 장 놓여 있거나 화장대 위에 달러가 뒹굴고 있다면 그것은 도우미의 인내력을 시험하는 것 밖에는 안된다. 견물생심! 집주인이 먼저 조심해야 한다.

• 업체 혹은 어플 등의 신원을 보장해주는 곳이 아닌 개인적으로 도우미와 정기 계약을 맺을 경우 신분증 사본을 반드시 받아야한다. 전화번호와 함께 신분증사본을 달라고 하자, 웃으면서.

• 청소 어플은 랜덤으로 도우미를 매칭해주는데, 내가 원하는 도우미를 선택해서 예약할 경우 가격이 조금 올라간다. 두어 달 일해본 후 손발이 맞는 도우미를 찾았다면 그분과 직접 약속을 맺어 집으로 부르자. 어플보다 조금 저렴하게 이용할 수 있거나 도우미의 실질 소득이 늘어난다.

• 도우미의 청소스타일이 나와 맞지 않을 수 있으니 지켜보면서 코칭해야한다. 필자의 첫 도우미는 빗자루로 베란다 먼저 쓸고 난 후에 실내를 쓸어내려고 했다. 가만히 두면 행주로 변기도 닦는다. 다림질은 못 하는게 정상이다. 직접 하는 것을 보여주며 가르쳐야 한다. 글. 김유리

About chaovietnam

chaovietnam

Check Also

Travel – 베트남 불교의 성지 ‘옌뚜산’

하노이에서 차로 2시간 30분, 하롱베이에서 1시간 거리. 베트남 광닌(Quảng Ninh)성에 위치한 옌뚜(Yên Tử)산은 해발 1,068m의 …

답글 남기기

Translate »
Verified by MonsterInsights