This commit is contained in:
kjy
2025-12-16 20:06:49 +09:00
parent 699224fca0
commit b6d69c3623
24 changed files with 425 additions and 129 deletions

View File

@@ -5,12 +5,17 @@ import type {
ArticleResponse,
NaverRealEstateConfig,
} from "../types/naver.types";
import prisma from "../lib/prisma";
import { prisma } from "../../lib/prisma";
import type { RealEstateArticle } from "../generated/prisma";
import { findNearest } from "geolib";
import http from "http";
import https from "https";
// axios 인스턴스 생성 및 retry 설정
const axiosInstance = axios.create();
const axiosInstance = axios.create({
// httpAgent: new http.Agent({ keepAlive: false }),
// httpsAgent: new https.Agent({ keepAlive: false }),
});
axiosRetry(axiosInstance, {
retries: 4, // 4번 재시도
retryDelay: (retryCount: number) => {
@@ -27,6 +32,7 @@ export class NaverRealEstate {
async getRankingToken() {
try {
const response = await axios.get("https://new.land.naver.com/houses", {
adapter: "fetch",
proxy: {
host: "gw.dataimpulse.com",
port: 823,
@@ -42,6 +48,7 @@ export class NaverRealEstate {
e: "RETAIL",
},
headers: {
connection: "close",
accept:
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"accept-language": "ko;q=0.8",
@@ -159,6 +166,7 @@ export class NaverRealEstate {
method: "post",
maxBodyLength: Infinity,
url: "https://fin.land.naver.com/front-api/v1/realtor/articles",
adapter: "fetch",
proxy: {
host: "gw.dataimpulse.com",
port: 823,
@@ -261,6 +269,7 @@ export class NaverRealEstate {
const response = await axiosInstance.post<{
result: ArticleResponse;
}>(`${this.baseUrl}/front-api/v1/realtor/articles`, requestData, {
adapter: "fetch",
headers: {
accept: "application/json, text/plain, */*",
"accept-language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7",
@@ -309,7 +318,7 @@ export class NaverRealEstate {
}
// API 요청 간격 (선택사항)
const waitTime = 1000 + Math.floor(Math.random() * 1000);
const waitTime = 2000 + Math.floor(Math.random() * 1000);
console.log(`${waitTime}ms 후 다음 페이지 시도...`);
await new Promise((resolve) => setTimeout(resolve, waitTime));
} catch (error) {
@@ -540,6 +549,7 @@ export class NaverRealEstate {
}
}
async resetActiveStatus() {
console.log("초기화하는중");
return prisma.realEstateArticle.updateMany({
where: {
realtorId: this.realtorId,
@@ -577,6 +587,7 @@ export class NaverRealEstate {
const response = await axios.get(
`https://fin.land.naver.com/articles/${articleNumber}`,
{
adapter: "fetch",
proxy: {
host: "gw.dataimpulse.com",
port: 823,
@@ -794,6 +805,7 @@ export class NaverRealEstate {
const firstPageResponse = await axiosInstance.get(
`https://m.land.naver.com/agency/info/list?rltrMbrId=${this.realtorId}&tradTpCd=&atclRletTpCd=&tradeTypeChange=false&page=1`,
{
adapter: "fetch",
proxy: {
host: "gw.dataimpulse.com",
port: 823,
@@ -803,6 +815,7 @@ export class NaverRealEstate {
},
},
headers: {
connection: "close",
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",
"accept-language": "ko-KR,ko;q=0.9",
@@ -823,16 +836,26 @@ export class NaverRealEstate {
const totalCnt = firstPageResponse.data.totalCnt;
const totalPage = Math.ceil(totalCnt / 20);
for (let i = 1; i <= totalPage; i += 20) {
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
const BATCH_SIZE = 3;
const DELAY = 10000; // 10초
for (let i = 1; i <= totalPage; i += BATCH_SIZE) {
const group = [];
// i ~ i+19 페이지까지 묶기
for (let page = i; page < i + 20 && page <= totalPage; page++) {
for (let page = i; page < i + BATCH_SIZE && page <= totalPage; page++) {
group.push(updateAddress(page, this.realtorId));
}
// 병렬 실행 후 기다리기
await Promise.all(group);
console.log(`Requesting pages: ${i} ~ ${i + group.length - 1}`);
await Promise.allSettled(group);
console.log(`Batch done. Waiting ${DELAY / 1000}s...`);
await sleep(DELAY);
}
console.log("✅ 모든 페이지 처리 완료");
@@ -844,6 +867,7 @@ export class NaverRealEstate {
const response = await axiosInstance.get(
`https://m.land.naver.com/agency/info/list?rltrMbrId=${realtorId}&tradTpCd=&atclRletTpCd=&tradeTypeChange=false&page=${page}`,
{
adapter: "fetch",
proxy: {
host: "gw.dataimpulse.com",
port: 823,
@@ -853,6 +877,7 @@ export class NaverRealEstate {
},
},
headers: {
connection: "close",
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",
"accept-language": "ko-KR,ko;q=0.9",
@@ -930,6 +955,7 @@ export class NaverRealEstate {
const url = `https://m.land.naver.com/cluster/clusterList?view=atcl&rletTpCd=APT:OPST:VL:YR:DSD:ABYG:OBYG:JGC:JWJT:DDDGG:SGJT:JGB:OR:SG:SMS:GJCG:GM:TJ:APTHGJ&tradTpCd=A1:B1:B2:B3&z=19&lat=${article.yCoordinate}&lon=${article.xCoordinate}&btm=${btm}&lft=${lft}&top=${top}&rgt=${rgt}&pCortarNo=&addon=COMPLEX&isOnlyIsale=false`;
const response = await axiosInstance.get(url, {
adapter: "fetch",
proxy: {
host: "gw.dataimpulse.com",
port: 823,
@@ -939,6 +965,7 @@ export class NaverRealEstate {
},
},
headers: {
Connection: "close",
accept: "application/json, text/javascript, */*; q=0.01",
"accept-language": "ko;q=0.7",
"cache-control": "no-cache",
@@ -1013,6 +1040,7 @@ export class NaverRealEstate {
const url = `https://new.land.naver.com/api/articles?markerId=${article.lgeo}&markerType=LGEOHASH_MIX_ARTICLE&prevScrollTop=0&order=rank&realEstateType=${article.realEstateType}&tradeType=${article.tradeType}&rentPriceMin=0&rentPriceMax=900000000&priceMin=0&priceMax=900000000&areaMin=0&areaMax=900000000&oldBuildYears&recentlyBuildYears&minHouseHoldCount&maxHouseHoldCount&showArticle=false&sameAddressGroup=false&minMaintenanceCost&maxMaintenanceCost&priceType=RETAIL&directions=&page=1&articleState`;
const response = await axiosInstance.get(url, {
adapter: "fetch",
proxy: {
host: "gw.dataimpulse.com",
port: 823,
@@ -1022,6 +1050,7 @@ export class NaverRealEstate {
},
},
headers: {
Connection: "close",
accept: "*/*",
"accept-language":
"ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7,la;q=0.6,zh-CN;q=0.5,zh;q=0.4",
@@ -1071,6 +1100,7 @@ export class NaverRealEstate {
const url = `https://fin.land.naver.com/front-api/v1/article/agent?articleNumber=${articleNumber}`;
const response = await axiosInstance.get(url, {
adapter: "fetch",
proxy: {
host: "gw.dataimpulse.com",
port: 823,
@@ -1140,6 +1170,7 @@ export class NaverRealEstate {
const initialResponse = await axios.get(
"https://fin.land.naver.com/?content=recent",
{
adapter: "fetch",
proxy: {
host: "gw.dataimpulse.com",
port: 823,

View File

@@ -1,6 +1,6 @@
import type { RealEstateArticle } from "../generated/prisma";
import type { NaverRealEstate } from "./naver.service";
import prisma from "../lib/prisma";
import { prisma } from "../../lib/prisma";
import pLimit from "p-limit";
export class RankingService {
@@ -11,9 +11,12 @@ export class RankingService {
async updateRanking(articles: RealEstateArticle[], checkDate: Date) {
const { token, cookie } = await this.naver.getRankingToken();
const limit = pLimit(20);
const tasks = articles.map((article) => {
return limit(async () => {
for (let i = 0; i < articles.length; i += 20) {
const batch = articles.slice(i, i + 20);
console.log(`🚀 ${i + 1} ~ ${i + batch.length}번 매물 처리 시작`);
const tasks = batch.map(async (article) => {
try {
// cortarNo, lgeo 확인
if (!article.cortarNo || !article.lgeo) {
@@ -24,8 +27,10 @@ export class RankingService {
article.lgeo = lgeo;
}
// 랭킹 조회 및 업데이트
// 랭킹 조회
const ranking = await this.naver.getRanking(article, token, cookie);
// DB 업데이트
await prisma.realEstateArticle.update({
where: { id: article.id },
data: {
@@ -34,17 +39,23 @@ export class RankingService {
},
});
console.log(
`${article.articleNumber} - 랭킹 업데이트 완료: ${ranking}`
);
} catch (error) {
console.error(`❌ updateRanking 오류:`, error);
console.log(`${article.articleNumber} - 랭킹 완료 : ${ranking}`);
} catch (err) {
console.error(`❌ 오류: ${article.articleNumber}`, err);
}
});
});
console.log("🔹 예약된 작업 수:", tasks.length);
const results = await Promise.allSettled(tasks);
console.log("🟢 모든 limit 작업 완료:", results.length);
// **이 10개(또는 마지막 batch)가 모두 끝날 때까지 기다림**
await Promise.allSettled(tasks);
// 다음 배치가 남아있다면 딜레이
if (i + 20 < articles.length) {
console.log(`⏸ 10개 처리 완료 → 10초 휴식`);
await Bun.sleep(5000);
}
}
console.log("🎉 전체 랭킹 업데이트 완료!");
await Bun.sleep(100);
}

View File

@@ -76,6 +76,20 @@ export const telegramUsers: TelegramUser[] = [
phone: "010-4199-9650",
realtorId: "namyeong00",
},
// {
// site: "ALL",
// chatId: 6843597951,
// name: "강승원",
// phone: "010-5947-0000",
// realtorId: "diahouse1114",
// },
// {
// site: "ALL",
// chatId: 6843597951,
// name: "강승원",
// phone: "010-5947-0000",
// realtorId: "jdre0125",
// },
];
export const testUsers: TelegramUser[] = [
@@ -192,7 +206,7 @@ export class TelegramService {
})
);
await Promise.all(promises);
await Promise.allSettled(promises);
console.log(`${users.length}명에게 메시지 브로드캐스트 완료`);
}