feat: 위시켓·카카오 마케팅 가이드 추가 + 부동산 크롤링 프로그램 v1.0
- MARKETING.md: 섹션 5 위시켓 프로필 (한 줄소개·자기소개·체크리스트·플랫폼 비교) - MARKETING.md: 섹션 6 카카오 오픈채팅방 운영 가이드 (공지·입장메시지·파일탭·운영루틴) - public/downloads/real_estate_crawler_v1.0.py: 부동산 매물 크롤링 프로그램 지원: 직방·다방·피터팬·네이버부동산 출력: 플랫폼별 시트 + 중복제거 + 스타일 Excel Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
579
MARKETING.md
Normal file
579
MARKETING.md
Normal file
@@ -0,0 +1,579 @@
|
|||||||
|
# 쟁승메이드 마케팅 플레이북
|
||||||
|
|
||||||
|
> 7년차 대기업 백엔드 개발자 박재오 · bgg8988@gmail.com · 010-3907-1392
|
||||||
|
> **핵심 포지셔닝**: 계약서 먼저 · 납기 패널티 명시 · 소스코드 100% 인도 · 1개월 AS · 연락 두절 없음
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 목차
|
||||||
|
|
||||||
|
1. [크몽 서비스 카피](#1-크몽-서비스-카피)
|
||||||
|
2. [숨고 서비스 카피](#2-숨고-서비스-카피)
|
||||||
|
3. [공통 운영 가이드](#3-공통-운영-가이드)
|
||||||
|
4. [추가 홍보 채널 전략](#4-추가-홍보-채널-전략)
|
||||||
|
5. [위시켓 프리랜서 프로필](#5-위시켓-프리랜서-프로필)
|
||||||
|
6. [카카오 오픈채팅방 운영 가이드](#6-카카오-오픈채팅방-운영-가이드)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 크몽 서비스 카피
|
||||||
|
|
||||||
|
> 크몽 전략: **키워드 검색 → 포트폴리오 클릭 → 구매** 흐름.
|
||||||
|
> 제목 키워드 앞배치, 소개문 구조화, 태그 최적화가 핵심.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1-1. 외주 개발
|
||||||
|
|
||||||
|
**제목**
|
||||||
|
```
|
||||||
|
[7년차 대기업 개발자] 맞춤형 소프트웨어 외주개발 · 계약서 작성 · 소스코드 전달
|
||||||
|
```
|
||||||
|
|
||||||
|
**소개문**
|
||||||
|
```
|
||||||
|
안녕하세요, 7년차 대기업 백엔드 개발자 박재오입니다.
|
||||||
|
|
||||||
|
프리랜서 개발자를 찾다가 중간에 연락이 끊기거나,
|
||||||
|
완성물을 받지 못한 경험이 있으신가요?
|
||||||
|
|
||||||
|
저는 다릅니다.
|
||||||
|
|
||||||
|
✅ 계약서 먼저 작성합니다
|
||||||
|
✅ 납기일 지키고, 못 지키면 패널티 명시
|
||||||
|
✅ 완료 후 소스코드 100% 인도
|
||||||
|
✅ 1개월 무상 AS 기본 포함
|
||||||
|
✅ 주 1회 진행 상황 보고
|
||||||
|
|
||||||
|
─────────────────────────────
|
||||||
|
📌 주요 개발 분야
|
||||||
|
─────────────────────────────
|
||||||
|
• 업무 자동화 (Python RPA, 엑셀/이메일/보고서 자동화)
|
||||||
|
• 웹 서비스 개발 (Next.js, React, FastAPI)
|
||||||
|
• 데이터 수집·분석 시스템 (크롤링, 공공API 연동)
|
||||||
|
• 텔레그램 봇 / 알림 자동화
|
||||||
|
• 관리자 대시보드 / 사내 툴 개발
|
||||||
|
|
||||||
|
─────────────────────────────
|
||||||
|
📌 실제 납품 사례
|
||||||
|
─────────────────────────────
|
||||||
|
• 쇼핑몰 경쟁사 가격 모니터링 봇 → 수동 확인 0분/일
|
||||||
|
• Gmail 자동화 RPA → 이메일 처리 2시간 → 10분
|
||||||
|
• 영업 일보 자동화 → 보고서 작성 3시간 → 5분
|
||||||
|
• 주식 자동 매매 시스템 (직접 운영 중)
|
||||||
|
|
||||||
|
─────────────────────────────
|
||||||
|
📌 진행 방식
|
||||||
|
─────────────────────────────
|
||||||
|
1단계. 무료 상담 (요구사항 정리)
|
||||||
|
2단계. 견적서 + 계약서 작성
|
||||||
|
3단계. 개발 착수 (주 1회 보고)
|
||||||
|
4단계. 검수 + 소스코드 인도
|
||||||
|
5단계. 1개월 무상 AS
|
||||||
|
|
||||||
|
처음 외주를 맡기시는 분도 걱정 없이 진행할 수 있도록 단계마다 안내드립니다.
|
||||||
|
```
|
||||||
|
|
||||||
|
**패키지**
|
||||||
|
|
||||||
|
| 구분 | 가격 | 내용 | 기간 | AS |
|
||||||
|
|------|------|------|------|----|
|
||||||
|
| 베이직 | 30만원~ | 단순 스크립트·봇 | 1~2주 | 1개월 |
|
||||||
|
| 스탠다드 | 80만원~ | 자동화 시스템·API 연동 | 2~4주 | 1개월 |
|
||||||
|
| 프리미엄 | 200만원~ | 풀스택 웹서비스 | 4~8주 | 3개월 |
|
||||||
|
|
||||||
|
**태그**
|
||||||
|
```
|
||||||
|
외주개발, 프리랜서개발자, 파이썬개발, 업무자동화, 웹개발, RPA, 소프트웨어개발, 맞춤개발, 백엔드개발, 자동화프로그램
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1-2. 업무 자동화
|
||||||
|
|
||||||
|
**제목**
|
||||||
|
```
|
||||||
|
[7년차 개발자] 엑셀·이메일·보고서 업무 자동화 개발 · Python RPA · 반복업무 제거
|
||||||
|
```
|
||||||
|
|
||||||
|
**소개문**
|
||||||
|
```
|
||||||
|
안녕하세요, 박재오입니다.
|
||||||
|
매일 반복하는 업무, 자동화하면 하루 몇 시간을 돌려받을 수 있습니다.
|
||||||
|
|
||||||
|
─────────────────────────────
|
||||||
|
📌 이런 분께 딱 맞습니다
|
||||||
|
─────────────────────────────
|
||||||
|
☑ 매일 같은 엑셀 파일을 수작업으로 정리하고 있다면
|
||||||
|
☑ 이메일 분류·답장 초안을 매번 손으로 작성한다면
|
||||||
|
☑ 보고서를 만드는 데 매주 2~3시간씩 쏟고 있다면
|
||||||
|
☑ 여러 사이트에서 데이터를 직접 긁어 모으고 있다면
|
||||||
|
|
||||||
|
─────────────────────────────
|
||||||
|
📌 자동화 가능한 업무
|
||||||
|
─────────────────────────────
|
||||||
|
• 엑셀 데이터 집계 → 보고서 자동 생성 (PDF/이메일 발송)
|
||||||
|
• 이메일 자동 분류 · 답변 초안 작성
|
||||||
|
• 웹사이트 데이터 자동 수집 (크롤링)
|
||||||
|
• 경쟁사 가격 모니터링 + 텔레그램 알림
|
||||||
|
• 공공데이터 API 연동 자동 수집
|
||||||
|
• PPT 자동 생성 (데이터 기반)
|
||||||
|
• 카카오톡·텔레그램·슬랙 알림 봇
|
||||||
|
|
||||||
|
─────────────────────────────
|
||||||
|
📌 실제 납품 결과
|
||||||
|
─────────────────────────────
|
||||||
|
"보고서 작성 3시간 → 5분, 매일 09:00 자동 발송" — 영업팀 고객
|
||||||
|
"이메일 처리 일 2시간 → 10분" — 무역업 고객
|
||||||
|
"경쟁사 50개 상품 매일 자동 추적, 수동 확인 0분" — 쇼핑몰 고객
|
||||||
|
|
||||||
|
─────────────────────────────
|
||||||
|
📌 진행 방식
|
||||||
|
─────────────────────────────
|
||||||
|
① 무료 상담 → 자동화 가능 여부 판단
|
||||||
|
② 견적 + 계약서 작성
|
||||||
|
③ 개발 + 테스트
|
||||||
|
④ 소스코드 인도 + 사용법 가이드 문서
|
||||||
|
⑤ 1개월 무상 AS
|
||||||
|
|
||||||
|
자동화가 가능한지 확인만 해도 됩니다. 부담 없이 먼저 문의해 주세요.
|
||||||
|
```
|
||||||
|
|
||||||
|
**패키지**
|
||||||
|
|
||||||
|
| 구분 | 가격 | 내용 | 기간 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 베이직 | 15만원~ | 단일 반복 업무 자동화 | 3~7일 |
|
||||||
|
| 스탠다드 | 40만원~ | 복합 자동화 + 알림 연동 | 1~2주 |
|
||||||
|
| 프리미엄 | 100만원~ | 다부서 통합 자동화 시스템 | 2~4주 |
|
||||||
|
|
||||||
|
**태그**
|
||||||
|
```
|
||||||
|
업무자동화, 엑셀자동화, Python자동화, RPA, 보고서자동화, 크롤링, 이메일자동화, 텔레그램봇, 반복업무, 자동화프로그램
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1-3. 홈페이지 제작
|
||||||
|
|
||||||
|
**제목**
|
||||||
|
```
|
||||||
|
[7년차 개발자] 반응형 홈페이지 · 랜딩페이지 · 소개페이지 제작 · 직접 개발 · 템플릿 X
|
||||||
|
```
|
||||||
|
|
||||||
|
**소개문**
|
||||||
|
```
|
||||||
|
안녕하세요, 박재오입니다.
|
||||||
|
템플릿 없이, 처음부터 직접 코딩합니다.
|
||||||
|
|
||||||
|
─────────────────────────────
|
||||||
|
📌 이런 분께 추천합니다
|
||||||
|
─────────────────────────────
|
||||||
|
☑ 업체 소개 / 서비스 소개 페이지가 필요한 소상공인
|
||||||
|
☑ 포트폴리오·이력서 사이트가 필요한 프리랜서
|
||||||
|
☑ 신규 서비스 런칭 랜딩페이지가 필요한 스타트업
|
||||||
|
☑ 워드프레스·카페24 없이 직접 관리하고 싶은 분
|
||||||
|
|
||||||
|
─────────────────────────────
|
||||||
|
📌 제작 방식
|
||||||
|
─────────────────────────────
|
||||||
|
• 템플릿 X — 디자인부터 퍼블리싱까지 직접 제작
|
||||||
|
• 모바일 완벽 대응 (반응형)
|
||||||
|
• 빠른 로딩 속도 (Next.js / React 기반)
|
||||||
|
• Vercel 무료 배포 포함 (도메인 연결 안내)
|
||||||
|
• 소스코드 100% 인도
|
||||||
|
|
||||||
|
─────────────────────────────
|
||||||
|
📌 포함 항목
|
||||||
|
─────────────────────────────
|
||||||
|
✅ 기획 상담 1회
|
||||||
|
✅ 화면 설계 (와이어프레임)
|
||||||
|
✅ 반응형 개발
|
||||||
|
✅ 문의 폼 연동 (이메일 수신)
|
||||||
|
✅ 배포 + 도메인 연결 안내
|
||||||
|
✅ 1개월 무상 수정
|
||||||
|
|
||||||
|
─────────────────────────────
|
||||||
|
📌 기간 및 비용
|
||||||
|
─────────────────────────────
|
||||||
|
• 단일 페이지 (랜딩): 2~5일 / 50만원~
|
||||||
|
• 5페이지 이하 소개 사이트: 1~2주 / 100만원~
|
||||||
|
• 관리자 기능 포함: 2~4주 / 200만원~
|
||||||
|
```
|
||||||
|
|
||||||
|
**태그**
|
||||||
|
```
|
||||||
|
홈페이지제작, 랜딩페이지, 반응형웹, 소개페이지, 웹개발, Next.js, React, 소상공인홈페이지, 포트폴리오사이트, 직접개발
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 숨고 서비스 카피
|
||||||
|
|
||||||
|
> 숨고 전략: **고객이 요청 → 전문가가 제안** 흐름.
|
||||||
|
> 제안서 첫 줄이 클릭률 결정. 간결함 + 신뢰 + 인간미가 핵심.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2-1. 외주 개발
|
||||||
|
|
||||||
|
**프로필 한 줄 소개**
|
||||||
|
```
|
||||||
|
7년차 대기업 백엔드 개발자 · 계약서 먼저 쓰고, 납기 지키고, 소스코드 드립니다
|
||||||
|
```
|
||||||
|
|
||||||
|
**제안서 본문**
|
||||||
|
```
|
||||||
|
안녕하세요, 박재오입니다.
|
||||||
|
|
||||||
|
요청 내용 잘 읽었습니다.
|
||||||
|
[고객 요청 핵심 한 줄 요약] 작업이 필요하시군요.
|
||||||
|
|
||||||
|
비슷한 프로젝트 경험이 있어 충분히 도와드릴 수 있습니다.
|
||||||
|
|
||||||
|
─────────────────
|
||||||
|
저는 이렇게 합니다
|
||||||
|
─────────────────
|
||||||
|
✅ 진행 전에 계약서부터 씁니다 (구두 약속 X)
|
||||||
|
✅ 납기일은 반드시 지킵니다 — 못 지키면 패널티 명시
|
||||||
|
✅ 개발 중 주 1회 진행 상황 보고
|
||||||
|
✅ 완료 후 소스코드 100% 드립니다
|
||||||
|
✅ 1개월은 무상으로 수정·보완해드립니다
|
||||||
|
|
||||||
|
개발자 찾다가 연락이 끊기거나 결과물을 못 받으신 분들이
|
||||||
|
많으셔서, 저는 처음부터 이 부분을 확실히 약속드립니다.
|
||||||
|
|
||||||
|
먼저 30분 정도 무료로 상담해드리겠습니다.
|
||||||
|
어떤 기능이 필요하신지 편하게 말씀해 주세요.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2-2. 업무 자동화
|
||||||
|
|
||||||
|
**프로필 한 줄 소개**
|
||||||
|
```
|
||||||
|
반복 업무 자동화 전문 · 엑셀·이메일·보고서·크롤링 · 실제 운영 중인 자동화 시스템 다수
|
||||||
|
```
|
||||||
|
|
||||||
|
**제안서 본문**
|
||||||
|
```
|
||||||
|
안녕하세요, 박재오입니다.
|
||||||
|
|
||||||
|
[고객 요청 업무] 자동화, 충분히 가능합니다.
|
||||||
|
|
||||||
|
직접 운영 중인 자동화 시스템이 여러 개 있고,
|
||||||
|
비슷한 의뢰를 여럿 납품해드렸습니다.
|
||||||
|
|
||||||
|
─── 최근 비슷한 사례 ───
|
||||||
|
• 영업팀 일보 자동화 → 보고서 작성 3시간 → 5분
|
||||||
|
• 쇼핑몰 가격 모니터링 → 수동 확인 완전 제거
|
||||||
|
• Gmail 자동화 → 이메일 처리 2시간 → 10분
|
||||||
|
|
||||||
|
자동화가 가능한지 모르겠다고 하셔도 괜찮습니다.
|
||||||
|
먼저 무료로 확인해드리겠습니다.
|
||||||
|
|
||||||
|
계약서 작성 후 착수, 소스코드 전달, 1개월 AS까지 기본입니다.
|
||||||
|
편하게 연락 주세요.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2-3. 홈페이지 제작
|
||||||
|
|
||||||
|
**프로필 한 줄 소개**
|
||||||
|
```
|
||||||
|
홈페이지 직접 개발 · 템플릿 X · 반응형 · 소스코드 전달 · 배포까지
|
||||||
|
```
|
||||||
|
|
||||||
|
**제안서 본문**
|
||||||
|
```
|
||||||
|
안녕하세요, 박재오입니다.
|
||||||
|
|
||||||
|
[고객 업종/목적] 홈페이지 제작, 도와드리겠습니다.
|
||||||
|
|
||||||
|
템플릿이나 워드프레스 없이 처음부터 직접 코딩합니다.
|
||||||
|
그래서 원하시는 대로 만들어드릴 수 있습니다.
|
||||||
|
|
||||||
|
─── 기본 포함 사항 ───
|
||||||
|
✅ 모바일 완벽 대응
|
||||||
|
✅ 빠른 로딩 (Next.js 기반)
|
||||||
|
✅ 문의 폼 연동 (이메일 수신)
|
||||||
|
✅ 배포 + 도메인 연결
|
||||||
|
✅ 소스코드 전달
|
||||||
|
✅ 1개월 무상 수정
|
||||||
|
|
||||||
|
계약서 먼저 쓰고, 납기 지키고, 중간 보고도 드립니다.
|
||||||
|
개발자 연락 두절 걱정 없이 맡기실 수 있습니다.
|
||||||
|
|
||||||
|
먼저 어떤 페이지가 필요하신지 말씀해 주세요.
|
||||||
|
같이 정리해드리겠습니다.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 공통 운영 가이드
|
||||||
|
|
||||||
|
| 항목 | 크몽 | 숨고 |
|
||||||
|
|------|------|------|
|
||||||
|
| **제목 전략** | 키워드 앞배치 + 대괄호 경력 표시 | 프로필 한 줄로 차별점 압축 |
|
||||||
|
| **가격 노출** | 패키지 3단계 명시 | 최저가 노출 후 상담 유도 |
|
||||||
|
| **응답 속도** | 24시간 이내 응답 뱃지 목표 | 요청 후 1시간 이내 제안 발송 |
|
||||||
|
| **후기 전략** | 초반 3건 지인 의뢰로 확보 | 5점 후기 누적 → 노출 순위 상승 |
|
||||||
|
| **CTA** | "무료 상담 문의" 버튼 | 제안서 발송 → 카카오 연결 |
|
||||||
|
|
||||||
|
**어디서든 반복할 핵심 5문장**
|
||||||
|
```
|
||||||
|
계약서 먼저 작성합니다.
|
||||||
|
납기일을 지킵니다. 못 지키면 패널티를 명시합니다.
|
||||||
|
완료 후 소스코드 100% 드립니다.
|
||||||
|
1개월 무상 AS가 기본입니다.
|
||||||
|
연락 두절 없습니다.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 추가 홍보 채널 전략
|
||||||
|
|
||||||
|
### 4-1. 콘텐츠 마케팅 (무료 · 장기)
|
||||||
|
|
||||||
|
#### 네이버 블로그
|
||||||
|
가장 빠르게 검색 유입을 만들 수 있는 채널. "외주 개발" 관련 정보성 글이 강점.
|
||||||
|
|
||||||
|
| 주제 예시 | 검색 의도 |
|
||||||
|
|-----------|-----------|
|
||||||
|
| `프리랜서 개발자 고르는 법 — 연락 두절 피하는 5가지 체크리스트` | 외주 개발 의뢰 예정자 |
|
||||||
|
| `엑셀 업무 자동화, 직접 해보기 vs 개발자 의뢰 — 비용 비교` | 자동화 관심자 |
|
||||||
|
| `소상공인 홈페이지 제작 비용 현실적으로 알아보기` | 홈페이지 필요 소상공인 |
|
||||||
|
| `파이썬으로 내 업무 자동화하기 — 실제 사례 3가지` | 자동화 입문자 |
|
||||||
|
| `크몽 외주 개발 의뢰 전에 꼭 확인해야 할 것들` | 크몽 잠재 고객 |
|
||||||
|
|
||||||
|
> **운영 팁**: 글 말미에 "무료 상담 링크(쟁승메이드)" 자연스럽게 삽입. 월 4~8편 꾸준히.
|
||||||
|
|
||||||
|
#### 유튜브 / 쇼츠
|
||||||
|
보여주기 콘텐츠가 신뢰도를 폭발적으로 높임.
|
||||||
|
|
||||||
|
| 영상 아이디어 | 형식 |
|
||||||
|
|---------------|------|
|
||||||
|
| `엑셀 3시간 업무, 자동화하면 5분 됩니다 (실제 시연)` | 쇼츠 60초 |
|
||||||
|
| `개발자 외주 맡기다 돈 날린 실제 사례 — 계약서 없이 진행하면 생기는 일` | 롱폼 7~10분 |
|
||||||
|
| `텔레그램 봇 만들어서 가격 모니터링 자동화 — 실제 코드 공개` | 롱폼 15분 |
|
||||||
|
| `내가 직접 만든 주식 자동매매 프로그램 — 2년째 운영 중` | 롱폼 10분 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4-2. 커뮤니티 마케팅 (무료 · 즉효)
|
||||||
|
|
||||||
|
직접 링크 홍보보다 **도움 주는 댓글 → 자연스러운 유입** 방식이 효과적.
|
||||||
|
|
||||||
|
| 커뮤니티 | 공략 방법 |
|
||||||
|
|----------|-----------|
|
||||||
|
| **클리앙 · 루리웹** | "자동화 가능한가요?" 류 질문 글에 실제 사례 답변 + 프로필 링크 |
|
||||||
|
| **네이버 카페 (스타트업, 소상공인)** | "개발자 구해요" 글에 제안, 정보성 글 기고 |
|
||||||
|
| **오픈카카오 (사업자/스타트업 채널)** | 자동화·개발 관련 질문에 전문 답변 |
|
||||||
|
| **링크드인** | 프로젝트 케이스 스터디 포스팅 (Before → After 수치 공개) |
|
||||||
|
| **X (트위터)** | 자동화 팁 쓰레드 → 사이트 링크 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4-3. 포트폴리오 플랫폼 등록 (무료)
|
||||||
|
|
||||||
|
| 플랫폼 | 특징 | 등록 방법 |
|
||||||
|
|--------|------|-----------|
|
||||||
|
| **위시켓** | B2B 프로젝트 중심, 단가 높음 | 프리랜서 프로필 + 포트폴리오 등록 |
|
||||||
|
| **라우드소싱** | 디자인·개발 공모전·의뢰 혼합 | 프리랜서 등록, 프로젝트 입찰 |
|
||||||
|
| **탈잉** | 재능 판매 + 강의 | 자동화 강의 or 1:1 컨설팅 |
|
||||||
|
| **오투잡** | 소규모 의뢰 다수 | 단순 업무 자동화·스크립트 판매 |
|
||||||
|
| **GitHub 프로필** | 개발자 신뢰도 핵심 | README에 포트폴리오·연락처 정리 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4-4. 네트워킹 (오프라인·온라인)
|
||||||
|
|
||||||
|
| 활동 | 기대 효과 |
|
||||||
|
|------|-----------|
|
||||||
|
| **IT 밋업·해커톤 참가** | 잠재 고객 직접 만남, 명함 배포 |
|
||||||
|
| **소상공인 협회·상공회의소** | 디지털 전환 수요 높은 실사용자층 접근 |
|
||||||
|
| **스타트업 스쿨 / 엑셀러레이터 행사** | MVP 개발 의뢰 연결 |
|
||||||
|
| **지인 추천 인센티브** | 소개 성사 시 다음 의뢰 10% 할인 제공 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4-5. 유료 광고 (예산 있을 때)
|
||||||
|
|
||||||
|
| 채널 | 예산 | 타겟 키워드 |
|
||||||
|
|------|------|-------------|
|
||||||
|
| **네이버 검색광고** | 월 10~30만원 | `외주 개발`, `업무 자동화 개발`, `홈페이지 제작` |
|
||||||
|
| **카카오 비즈보드** | 월 10~20만원 | 소상공인·자영업자 타겟팅 |
|
||||||
|
| **구글 검색광고** | 월 10만원~ | `python 자동화 외주`, `프리랜서 개발자` |
|
||||||
|
|
||||||
|
> **우선순위**: 콘텐츠 마케팅(블로그) → 커뮤니티 → 크몽/숨고 → 유료 광고 순서로 단계적 진행 권장.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4-6. 신뢰도 빌드업 로드맵
|
||||||
|
|
||||||
|
```
|
||||||
|
1개월차 크몽/숨고 등록 + 블로그 4편 작성 + 지인 후기 2~3건 확보
|
||||||
|
2개월차 쇼츠 영상 4개 + 커뮤니티 답변 활동 시작
|
||||||
|
3개월차 블로그 검색 유입 확인 + 크몽 리뷰 5개 달성 → 노출 순위 상승
|
||||||
|
6개월차 위시켓 등록 + 링크드인 케이스 스터디 포스팅
|
||||||
|
12개월차 재의뢰·소개 고객으로 신규 유입 없이도 수주 안정화
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 위시켓 프리랜서 프로필
|
||||||
|
|
||||||
|
> 위시켓 전략: **경력·기술 스택 중심** 플랫폼. 클라이언트가 검색하거나 먼저 제안을 보내는 구조.
|
||||||
|
> 크몽 대비 단가 높고 B2B 프로젝트 비중이 큼. 프로필 완성도 100%가 노출의 전제 조건.
|
||||||
|
|
||||||
|
### 한 줄 소개 (검색 키워드 포함)
|
||||||
|
```
|
||||||
|
7년차 대기업 백엔드 개발자 · Python 업무 자동화 · 웹 서비스 개발 · 납기 보장
|
||||||
|
```
|
||||||
|
|
||||||
|
### 자기소개 본문
|
||||||
|
```
|
||||||
|
안녕하세요, 7년차 대기업 백엔드 개발자 박재오입니다.
|
||||||
|
|
||||||
|
본업과 병행하며 업무 자동화·웹 개발 프리랜서 프로젝트를 진행하고 있습니다.
|
||||||
|
직접 운영 중인 서비스(주식 자동매매 시스템, 로또 분석 플랫폼)가 있어
|
||||||
|
설계부터 운영까지 전 과정을 직접 경험했습니다.
|
||||||
|
|
||||||
|
──────────────────────────
|
||||||
|
주요 기술
|
||||||
|
──────────────────────────
|
||||||
|
• Backend: Python, FastAPI, Node.js, Next.js
|
||||||
|
• 자동화: RPA, Selenium, Gmail API, Google Apps Script, OpenPyXL
|
||||||
|
• 데이터: PostgreSQL, SQLite, 공공데이터 API, 웹 크롤링
|
||||||
|
• 인프라: Vercel, NAS 자체 서버 운영, Supabase
|
||||||
|
|
||||||
|
──────────────────────────
|
||||||
|
진행 방식 (차별점)
|
||||||
|
──────────────────────────
|
||||||
|
✅ 계약서 먼저 작성 (구두 약속 없음)
|
||||||
|
✅ 납기일 명시 + 지연 시 패널티 조항
|
||||||
|
✅ 개발 중 주 1회 진행 보고
|
||||||
|
✅ 완료 후 소스코드 100% 인도
|
||||||
|
✅ 1개월 무상 AS
|
||||||
|
|
||||||
|
──────────────────────────
|
||||||
|
납품 사례
|
||||||
|
──────────────────────────
|
||||||
|
• 영업팀 일보 자동화 → 보고서 작성 3시간 → 5분
|
||||||
|
• 쇼핑몰 경쟁사 가격 모니터링 봇 → 수동 확인 0분/일
|
||||||
|
• Gmail 자동화 RPA → 이메일 처리 2시간 → 10분
|
||||||
|
|
||||||
|
포트폴리오: jaengseung-made.vercel.app
|
||||||
|
```
|
||||||
|
|
||||||
|
### 프로필 등록 체크리스트
|
||||||
|
|
||||||
|
```
|
||||||
|
□ 프로필 사진 (전문적인 사진 or 깔끔한 단색 배경)
|
||||||
|
□ 기술 스택 태그 최대한 추가 (Python, Next.js, RPA, FastAPI 등)
|
||||||
|
□ 포트폴리오 URL 입력 (jaengseung-made.vercel.app)
|
||||||
|
□ 희망 단가: 시간당 5~7만원 (초반, 경력 쌓이면 상향)
|
||||||
|
□ 가능 프로젝트 유형: 단기·중기 모두 체크
|
||||||
|
□ 프로필 완성도 100% (미완성 시 노출 차단됨)
|
||||||
|
□ 포트폴리오 파일 첨부 (PDF 1~2페이지)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 위시켓 vs 크몽/숨고 차이
|
||||||
|
|
||||||
|
| 항목 | 위시켓 | 크몽/숨고 |
|
||||||
|
|------|--------|-----------|
|
||||||
|
| **주 사용자** | 스타트업, 중소기업 | 개인, 소상공인 |
|
||||||
|
| **평균 단가** | 높음 (프로젝트 단위) | 낮음~중간 |
|
||||||
|
| **프로젝트 규모** | 중대형 | 소~중형 |
|
||||||
|
| **수수료** | 10~15% | 20% 내외 |
|
||||||
|
| **경쟁 방식** | 제안서 입찰 | 검색 노출 |
|
||||||
|
| **핵심 무기** | 경력·기술력 | 후기·가격 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 카카오 오픈채팅방 운영 가이드
|
||||||
|
|
||||||
|
> 오픈채팅 링크: https://open.kakao.com/o/s9stoNvb
|
||||||
|
|
||||||
|
### 채팅방 기본 설정
|
||||||
|
|
||||||
|
```
|
||||||
|
채팅방 이름: 쟁승메이드 · 개발 무료 상담
|
||||||
|
프로필 사진: 쟁승메이드 로고 이미지
|
||||||
|
채팅방 설명: 7년차 개발자의 무료 상담 채널
|
||||||
|
외주 개발 · 업무 자동화 · 홈페이지 제작
|
||||||
|
부담 없이 질문하세요 :)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 공지 (상단 고정) — 입장 즉시 보이는 텍스트
|
||||||
|
|
||||||
|
```
|
||||||
|
📌 쟁승메이드 무료 상담 채널입니다
|
||||||
|
|
||||||
|
안녕하세요, 7년차 대기업 개발자 박재오입니다.
|
||||||
|
개발 관련 고민이라면 무엇이든 편하게 물어보세요.
|
||||||
|
|
||||||
|
──────────────────
|
||||||
|
💬 상담 가능 분야
|
||||||
|
──────────────────
|
||||||
|
• 엑셀·이메일·보고서 업무 자동화
|
||||||
|
• 웹사이트·홈페이지 제작
|
||||||
|
• 맞춤형 소프트웨어 개발
|
||||||
|
• "이런 것도 되나요?" 가능 여부 확인
|
||||||
|
|
||||||
|
──────────────────
|
||||||
|
📋 상담 시작하는 법
|
||||||
|
──────────────────
|
||||||
|
아래 형식으로 남겨주시면 빠르게 답변드립니다.
|
||||||
|
|
||||||
|
[원하는 것]
|
||||||
|
[예산 범위 (대략적으로)]
|
||||||
|
[연락 가능 시간]
|
||||||
|
|
||||||
|
🌐 포트폴리오: jaengseung-made.vercel.app
|
||||||
|
```
|
||||||
|
|
||||||
|
### 입장 인사 메시지 (설정 위치: 관리 → 입장 메시지)
|
||||||
|
|
||||||
|
```
|
||||||
|
반갑습니다! 쟁승메이드 상담 채널에 오신 걸 환영합니다 😊
|
||||||
|
|
||||||
|
궁금하신 것 편하게 남겨주세요.
|
||||||
|
"이런 것도 자동화 되나요?" 같은 가벼운 질문도 좋습니다.
|
||||||
|
|
||||||
|
보통 1~2시간 내에 답변드립니다.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 파일 탭에 올려둘 문서
|
||||||
|
|
||||||
|
| 파일명 | 내용 | 목적 |
|
||||||
|
|--------|------|------|
|
||||||
|
| `쟁승메이드_서비스소개.pdf` | 서비스 목록 + 가격 요약 1페이지 | 신뢰 + 가격 가이드 |
|
||||||
|
| `업무자동화_체크리스트.pdf` | 자동화 가능 여부 자가진단 10문항 | 리드 필터링 |
|
||||||
|
| `외주개발_진행절차.pdf` | 계약~납품 5단계 플로우 | 프로세스 신뢰 확보 |
|
||||||
|
|
||||||
|
### 부재 시 공지 템플릿 (복사 사용)
|
||||||
|
|
||||||
|
```
|
||||||
|
⏰ 현재 업무 중입니다.
|
||||||
|
퇴근 후 19시 이후에 확인하겠습니다.
|
||||||
|
|
||||||
|
급하신 분은 아래 문의 폼을 이용해 주세요.
|
||||||
|
👉 jaengseung-made.vercel.app/freelance
|
||||||
|
```
|
||||||
|
|
||||||
|
### 운영 루틴
|
||||||
|
|
||||||
|
```
|
||||||
|
출근 전 (08:30) 전날 밤 문의 확인 + 답변
|
||||||
|
점심 (12:30) 빠른 확인 + 간단 답변
|
||||||
|
퇴근 후 (19:00) 상세 답변 + 견적 안내
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*최종 수정: 2026-03-23*
|
||||||
756
public/downloads/real_estate_crawler_v1.0.py
Normal file
756
public/downloads/real_estate_crawler_v1.0.py
Normal file
@@ -0,0 +1,756 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
===========================================================
|
||||||
|
부동산 매물 크롤링 프로그램 v1.0
|
||||||
|
쟁승메이드 | jaengseung-made.vercel.app
|
||||||
|
문의: bgg8988@gmail.com | 010-3907-1392
|
||||||
|
===========================================================
|
||||||
|
지원 플랫폼:
|
||||||
|
- 직방 (Zigbang)
|
||||||
|
- 다방 (Dabang)
|
||||||
|
- 피터팬 (Peterpanz)
|
||||||
|
- 네이버 부동산 (Naver Land)
|
||||||
|
|
||||||
|
수집 결과: Excel 파일 (.xlsx) — 플랫폼별 시트 + 전체 시트
|
||||||
|
|
||||||
|
필요 패키지 설치:
|
||||||
|
pip install requests beautifulsoup4 pandas openpyxl tqdm
|
||||||
|
===========================================================
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import random
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# ── 패키지 확인 ────────────────────────────────────────────
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
import pandas as pd
|
||||||
|
from openpyxl.styles import PatternFill, Font, Alignment, Border, Side
|
||||||
|
from openpyxl.utils import get_column_letter
|
||||||
|
except ImportError as e:
|
||||||
|
print(f"\n[오류] 필요한 패키지가 없습니다: {e}")
|
||||||
|
print("아래 명령어로 설치 후 다시 실행하세요:")
|
||||||
|
print(" pip install requests beautifulsoup4 pandas openpyxl tqdm\n")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
# ══════════════════════════════════════════════════════════
|
||||||
|
# 공통 설정
|
||||||
|
# ══════════════════════════════════════════════════════════
|
||||||
|
HEADERS = {
|
||||||
|
"User-Agent": (
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||||
|
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||||
|
"Chrome/122.0.0.0 Safari/537.36"
|
||||||
|
),
|
||||||
|
"Accept": "application/json, text/plain, */*",
|
||||||
|
"Accept-Language": "ko-KR,ko;q=0.9,en-US;q=0.8",
|
||||||
|
"Accept-Encoding": "gzip, deflate, br",
|
||||||
|
"Connection": "keep-alive",
|
||||||
|
}
|
||||||
|
|
||||||
|
DELAY_MIN = 1.2
|
||||||
|
DELAY_MAX = 2.8
|
||||||
|
|
||||||
|
|
||||||
|
def _delay():
|
||||||
|
"""요청 간 랜덤 딜레이 — 서버 부하 방지 및 차단 회피"""
|
||||||
|
time.sleep(random.uniform(DELAY_MIN, DELAY_MAX))
|
||||||
|
|
||||||
|
|
||||||
|
# ══════════════════════════════════════════════════════════
|
||||||
|
# 데이터 스키마
|
||||||
|
# ══════════════════════════════════════════════════════════
|
||||||
|
COLUMNS = [
|
||||||
|
"플랫폼", "매물유형", "거래유형",
|
||||||
|
"보증금(만원)", "월세(만원)", "가격표시",
|
||||||
|
"면적(㎡)", "층수", "주소", "건물명",
|
||||||
|
"설명", "등록일", "링크",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _item(**kwargs) -> dict:
|
||||||
|
base = {c: "" for c in COLUMNS}
|
||||||
|
base.update(kwargs)
|
||||||
|
return base
|
||||||
|
|
||||||
|
|
||||||
|
def _fmt(amount) -> str:
|
||||||
|
"""정수(만원) → '3억 2,000만' or '1,500만' 문자열"""
|
||||||
|
try:
|
||||||
|
amount = int(amount)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return str(amount)
|
||||||
|
if amount <= 0:
|
||||||
|
return "0"
|
||||||
|
if amount >= 10000:
|
||||||
|
eok = amount // 10000
|
||||||
|
man = amount % 10000
|
||||||
|
return f"{eok}억" if man == 0 else f"{eok}억 {man:,}만"
|
||||||
|
return f"{amount:,}만"
|
||||||
|
|
||||||
|
|
||||||
|
def _price_str(deposit, rent, trade_type: str) -> str:
|
||||||
|
if trade_type == "월세":
|
||||||
|
return f"{_fmt(deposit)} / {_fmt(rent)}"
|
||||||
|
if trade_type == "전세":
|
||||||
|
return f"전세 {_fmt(deposit)}"
|
||||||
|
return _fmt(deposit)
|
||||||
|
|
||||||
|
|
||||||
|
# ══════════════════════════════════════════════════════════
|
||||||
|
# 위경도 → Geohash 변환 (직방 API용)
|
||||||
|
# ══════════════════════════════════════════════════════════
|
||||||
|
def _to_geohash(lat: float, lng: float, precision: int = 5) -> str:
|
||||||
|
BASE32 = "0123456789bcdefghjkmnpqrstuvwxyz"
|
||||||
|
lat_r, lng_r = [-90.0, 90.0], [-180.0, 180.0]
|
||||||
|
gh, bits, bit, even = [], 0, 0, True
|
||||||
|
while len(gh) < precision:
|
||||||
|
if even:
|
||||||
|
mid = (lng_r[0] + lng_r[1]) / 2
|
||||||
|
if lng >= mid:
|
||||||
|
bits = (bits << 1) | 1; lng_r[0] = mid
|
||||||
|
else:
|
||||||
|
bits <<= 1; lng_r[1] = mid
|
||||||
|
else:
|
||||||
|
mid = (lat_r[0] + lat_r[1]) / 2
|
||||||
|
if lat >= mid:
|
||||||
|
bits = (bits << 1) | 1; lat_r[0] = mid
|
||||||
|
else:
|
||||||
|
bits <<= 1; lat_r[1] = mid
|
||||||
|
even = not even
|
||||||
|
bit += 1
|
||||||
|
if bit == 5:
|
||||||
|
gh.append(BASE32[bits]); bits = bit = 0
|
||||||
|
return "".join(gh)
|
||||||
|
|
||||||
|
|
||||||
|
# ══════════════════════════════════════════════════════════
|
||||||
|
# 주요 지역 위경도 DB
|
||||||
|
# ══════════════════════════════════════════════════════════
|
||||||
|
LOCATION_DB: dict[str, tuple[float, float]] = {
|
||||||
|
# 서울 자치구
|
||||||
|
"강남구": (37.5172, 127.0473), "서초구": (37.4837, 127.0324),
|
||||||
|
"송파구": (37.5145, 127.1059), "마포구": (37.5638, 126.9084),
|
||||||
|
"강서구": (37.5509, 126.8495), "영등포구": (37.5264, 126.8962),
|
||||||
|
"용산구": (37.5311, 126.9810), "성동구": (37.5634, 127.0369),
|
||||||
|
"동작구": (37.5124, 126.9393), "관악구": (37.4784, 126.9516),
|
||||||
|
"강북구": (37.6396, 127.0255), "노원구": (37.6543, 127.0568),
|
||||||
|
"은평구": (37.6026, 126.9291), "서대문구": (37.5791, 126.9368),
|
||||||
|
"종로구": (37.5735, 126.9789), "중구": (37.5641, 126.9979),
|
||||||
|
"성북구": (37.5894, 127.0167), "광진구": (37.5385, 127.0823),
|
||||||
|
"중랑구": (37.5953, 127.0843), "도봉구": (37.6688, 127.0471),
|
||||||
|
"강동구": (37.5301, 127.1238), "구로구": (37.4954, 126.8874),
|
||||||
|
"금천구": (37.4600, 126.9000), "양천구": (37.5170, 126.8666),
|
||||||
|
"동대문구": (37.5744, 127.0396),
|
||||||
|
# 주요 동/지역
|
||||||
|
"강남": (37.5172, 127.0473), "홍대": (37.5563, 126.9233),
|
||||||
|
"신촌": (37.5596, 126.9361), "이태원": (37.5346, 126.9944),
|
||||||
|
"합정": (37.5495, 126.9140), "연남동": (37.5607, 126.9224),
|
||||||
|
"성수동": (37.5443, 127.0557), "건대": (37.5402, 127.0702),
|
||||||
|
"혜화": (37.5820, 127.0019), "신림": (37.4843, 126.9292),
|
||||||
|
"사당": (37.4762, 126.9815), "잠실": (37.5133, 127.1000),
|
||||||
|
"여의도": (37.5216, 126.9245), "명동": (37.5607, 126.9862),
|
||||||
|
# 수도권
|
||||||
|
"인천": (37.4563, 126.7052), "수원": (37.2636, 127.0286),
|
||||||
|
"성남": (37.4201, 127.1267), "분당": (37.3825, 127.1175),
|
||||||
|
"판교": (37.3943, 127.1106), "일산": (37.6577, 126.7670),
|
||||||
|
"부천": (37.5035, 126.7660), "안양": (37.3943, 126.9568),
|
||||||
|
"의정부": (37.7381, 127.0337), "평택": (36.9921, 127.1130),
|
||||||
|
# 지방 광역시
|
||||||
|
"부산": (35.1796, 129.0756), "대구": (35.8714, 128.6014),
|
||||||
|
"대전": (36.3504, 127.3845), "광주": (35.1595, 126.8526),
|
||||||
|
"울산": (35.5384, 129.3114), "세종": (36.4800, 127.2890),
|
||||||
|
}
|
||||||
|
|
||||||
|
# 네이버 부동산 법정동 코드 주요 목록
|
||||||
|
CORTAR_CODES: dict[str, str] = {
|
||||||
|
"서울 강남구": "1168000000",
|
||||||
|
"서울 서초구": "1165000000",
|
||||||
|
"서울 송파구": "1171000000",
|
||||||
|
"서울 마포구": "1144000000",
|
||||||
|
"서울 용산구": "1117000000",
|
||||||
|
"서울 영등포구": "1156000000",
|
||||||
|
"서울 강서구": "1150000000",
|
||||||
|
"서울 관악구": "1162000000",
|
||||||
|
"서울 성동구": "1120000000",
|
||||||
|
"서울 동작구": "1159000000",
|
||||||
|
"서울 노원구": "1135000000",
|
||||||
|
"서울 은평구": "1138000000",
|
||||||
|
"서울 강동구": "1174000000",
|
||||||
|
"경기 성남시 분당구": "4113500000",
|
||||||
|
"경기 수원시 팔달구": "4111700000",
|
||||||
|
"경기 고양시 일산동구": "4128100000",
|
||||||
|
"부산 해운대구": "2635000000",
|
||||||
|
"부산 수영구": "2644000000",
|
||||||
|
"대구 수성구": "2723000000",
|
||||||
|
"인천 연수구": "2817000000",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_coords(address: str) -> tuple[float, float] | None:
|
||||||
|
for key, coords in LOCATION_DB.items():
|
||||||
|
if key in address:
|
||||||
|
return coords
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# ══════════════════════════════════════════════════════════
|
||||||
|
# 1. 직방 크롤러
|
||||||
|
# ══════════════════════════════════════════════════════════
|
||||||
|
class ZigbangCrawler:
|
||||||
|
"""직방 내부 API 기반 매물 수집"""
|
||||||
|
|
||||||
|
API = "https://apis.zigbang.com"
|
||||||
|
|
||||||
|
ROOM_CODE = {
|
||||||
|
"원룸": "원룸", "투룸": "투쓰리룸", "오피스텔": "오피스텔",
|
||||||
|
"아파트": "아파트", "빌라": "빌라",
|
||||||
|
}
|
||||||
|
TRADE_CODE = {"월세": "월세", "전세": "전세", "매매": "매매"}
|
||||||
|
|
||||||
|
def _geocode(self, address: str) -> tuple[float, float] | None:
|
||||||
|
"""직방 자체 검색 API로 위경도 조회"""
|
||||||
|
try:
|
||||||
|
r = requests.get(
|
||||||
|
f"{self.API}/v2/suggest",
|
||||||
|
params={"q": address, "serviceType": "all"},
|
||||||
|
headers=HEADERS, timeout=10,
|
||||||
|
)
|
||||||
|
for item in r.json().get("items", []):
|
||||||
|
lat = item.get("lat") or item.get("latitude")
|
||||||
|
lng = item.get("lng") or item.get("longitude")
|
||||||
|
if lat and lng:
|
||||||
|
return float(lat), float(lng)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def fetch(self, address: str, room_type: str, trade_type: str, limit: int = 100) -> list[dict]:
|
||||||
|
tag = f"[직방] {room_type}/{trade_type}"
|
||||||
|
print(f" {tag} 수집 중...", end=" ", flush=True)
|
||||||
|
|
||||||
|
coords = get_coords(address) or self._geocode(address)
|
||||||
|
if not coords:
|
||||||
|
print(f"위치 찾기 실패")
|
||||||
|
return []
|
||||||
|
|
||||||
|
lat, lng = coords
|
||||||
|
gh = _to_geohash(lat, lng, 5)
|
||||||
|
stype = self.ROOM_CODE.get(room_type, "원룸")
|
||||||
|
ttype = self.TRADE_CODE.get(trade_type, "월세")
|
||||||
|
|
||||||
|
url = (
|
||||||
|
f"{self.API}/v2/items"
|
||||||
|
f"?deposit_gteq=0&rent_gteq=0"
|
||||||
|
f"&sales_type_in={ttype}"
|
||||||
|
f"&service_type_eq={stype}"
|
||||||
|
f"&geohash={gh}&domain=zigbang&withCoords=true&per_page={limit}"
|
||||||
|
)
|
||||||
|
|
||||||
|
results = []
|
||||||
|
try:
|
||||||
|
_delay()
|
||||||
|
r = requests.get(url, headers={**HEADERS, "Referer": "https://www.zigbang.com/"}, timeout=15)
|
||||||
|
for it in r.json().get("items", []):
|
||||||
|
dep = int(it.get("deposit") or 0)
|
||||||
|
rent = int(it.get("rent") or 0)
|
||||||
|
item_id = it.get("item_id", "")
|
||||||
|
results.append(_item(
|
||||||
|
플랫폼="직방",
|
||||||
|
매물유형=room_type,
|
||||||
|
거래유형=trade_type,
|
||||||
|
**{"보증금(만원)": dep, "월세(만원)": rent},
|
||||||
|
가격표시=_price_str(dep, rent, trade_type),
|
||||||
|
**{"면적(㎡)": round(float(it.get("area") or 0), 1) or ""},
|
||||||
|
층수=str(it.get("floor") or ""),
|
||||||
|
주소=it.get("address") or it.get("local1") or "",
|
||||||
|
건물명=it.get("title") or "",
|
||||||
|
링크=f"https://www.zigbang.com/home/oneroom/items/{item_id}" if item_id else "",
|
||||||
|
))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"오류({e})")
|
||||||
|
return results
|
||||||
|
|
||||||
|
print(f"{len(results)}건")
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
# ══════════════════════════════════════════════════════════
|
||||||
|
# 2. 다방 크롤러
|
||||||
|
# ══════════════════════════════════════════════════════════
|
||||||
|
class DabangCrawler:
|
||||||
|
"""다방 내부 API 기반 매물 수집"""
|
||||||
|
|
||||||
|
API = "https://www.dabangapp.com/api/3"
|
||||||
|
|
||||||
|
ROOM_CODE = {"원룸": 1, "투룸": 2, "쓰리룸": 3, "오피스텔": 10, "아파트": 11, "빌라": 21}
|
||||||
|
TRADE_CODE = {"월세": 1, "전세": 2, "매매": 3}
|
||||||
|
|
||||||
|
def fetch(self, lat: float, lng: float, room_type: str, trade_type: str, limit: int = 80) -> list[dict]:
|
||||||
|
tag = f"[다방] {room_type}/{trade_type}"
|
||||||
|
print(f" {tag} 수집 중...", end=" ", flush=True)
|
||||||
|
|
||||||
|
rc = self.ROOM_CODE.get(room_type, 1)
|
||||||
|
tc = self.TRADE_CODE.get(trade_type, 1)
|
||||||
|
delta = 0.025
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"call_type": "web",
|
||||||
|
"device_type": "web",
|
||||||
|
"filters": json.dumps({
|
||||||
|
"sellingTypeList": [tc],
|
||||||
|
"roomTypeList": [rc],
|
||||||
|
"depositRange": {"min": 0, "max": 90000},
|
||||||
|
"priceRange": {"min": 0, "max": 999},
|
||||||
|
"areaRange": {"min": 0, "max": 999},
|
||||||
|
}),
|
||||||
|
"location": json.dumps([
|
||||||
|
[lat - delta, lng - delta],
|
||||||
|
[lat + delta, lng + delta],
|
||||||
|
]),
|
||||||
|
"page": 1,
|
||||||
|
"limit": limit,
|
||||||
|
"ordering": "-created_at",
|
||||||
|
}
|
||||||
|
|
||||||
|
results = []
|
||||||
|
try:
|
||||||
|
_delay()
|
||||||
|
r = requests.get(
|
||||||
|
f"{self.API}/room/list/map", params=params,
|
||||||
|
headers={**HEADERS, "Referer": "https://www.dabangapp.com/"}, timeout=15,
|
||||||
|
)
|
||||||
|
data = r.json()
|
||||||
|
rooms = data.get("rooms") or data.get("result", {}).get("rooms") or []
|
||||||
|
for room in rooms:
|
||||||
|
price = room.get("price") or [0, 0]
|
||||||
|
dep = int(price[0]) if isinstance(price, list) and price else 0
|
||||||
|
rent = int(price[1]) if isinstance(price, list) and len(price) > 1 else 0
|
||||||
|
rid = room.get("id", "")
|
||||||
|
results.append(_item(
|
||||||
|
플랫폼="다방",
|
||||||
|
매물유형=room_type,
|
||||||
|
거래유형=trade_type,
|
||||||
|
**{"보증금(만원)": dep, "월세(만원)": rent},
|
||||||
|
가격표시=_price_str(dep, rent, trade_type),
|
||||||
|
**{"면적(㎡)": round(float(room.get("area") or 0), 1) or ""},
|
||||||
|
층수=str(room.get("floor") or ""),
|
||||||
|
주소=room.get("address") or "",
|
||||||
|
건물명=room.get("name") or "",
|
||||||
|
설명=(room.get("title") or "")[:80],
|
||||||
|
링크=f"https://www.dabangapp.com/room/{rid}" if rid else "",
|
||||||
|
))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"오류({e})")
|
||||||
|
return results
|
||||||
|
|
||||||
|
print(f"{len(results)}건")
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
# ══════════════════════════════════════════════════════════
|
||||||
|
# 3. 피터팬 크롤러
|
||||||
|
# ══════════════════════════════════════════════════════════
|
||||||
|
class PeterpanzCrawler:
|
||||||
|
"""피터팬의 좋은방구하기 크롤러 (API + HTML 폴백)"""
|
||||||
|
|
||||||
|
BASE = "https://www.peterpanz.com"
|
||||||
|
TRADE_CODE = {"월세": "monthly", "전세": "charter", "매매": "selling"}
|
||||||
|
|
||||||
|
def fetch(self, lat: float, lng: float, trade_type: str, max_pages: int = 3) -> list[dict]:
|
||||||
|
tag = f"[피터팬] {trade_type}"
|
||||||
|
print(f" {tag} 수집 중...", end=" ", flush=True)
|
||||||
|
|
||||||
|
tc = self.TRADE_CODE.get(trade_type, "monthly")
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for page in range(1, max_pages + 1):
|
||||||
|
try:
|
||||||
|
_delay()
|
||||||
|
r = requests.get(
|
||||||
|
f"{self.BASE}/house/all",
|
||||||
|
params={"lat": lat, "lng": lng, "zoom": 13, "type": tc, "page": page},
|
||||||
|
headers={**HEADERS, "Referer": f"{self.BASE}/"},
|
||||||
|
timeout=15,
|
||||||
|
)
|
||||||
|
|
||||||
|
# JSON 응답 시도
|
||||||
|
try:
|
||||||
|
data = r.json()
|
||||||
|
houses = data.get("houses") or data.get("items") or []
|
||||||
|
if not houses:
|
||||||
|
break
|
||||||
|
for h in houses:
|
||||||
|
hid = h.get("idx") or h.get("id") or ""
|
||||||
|
dep = int(h.get("deposit") or 0)
|
||||||
|
rent = int(h.get("rent") or 0)
|
||||||
|
results.append(_item(
|
||||||
|
플랫폼="피터팬",
|
||||||
|
매물유형=h.get("type") or "",
|
||||||
|
거래유형=trade_type,
|
||||||
|
**{"보증금(만원)": dep, "월세(만원)": rent},
|
||||||
|
가격표시=_price_str(dep, rent, trade_type),
|
||||||
|
**{"면적(㎡)": h.get("area") or ""},
|
||||||
|
층수=str(h.get("floor") or ""),
|
||||||
|
주소=h.get("address") or "",
|
||||||
|
건물명=h.get("title") or "",
|
||||||
|
설명=(h.get("description") or "")[:80],
|
||||||
|
링크=f"{self.BASE}/house/detail/{hid}" if hid else "",
|
||||||
|
))
|
||||||
|
except (json.JSONDecodeError, ValueError):
|
||||||
|
# HTML 파싱 폴백
|
||||||
|
soup = BeautifulSoup(r.text, "html.parser")
|
||||||
|
cards = soup.select(".item-list li, .house-list-item, [class*='item']")
|
||||||
|
if not cards:
|
||||||
|
break
|
||||||
|
for card in cards:
|
||||||
|
title_el = card.select_one(".title, .name, h3, h4")
|
||||||
|
price_el = card.select_one(".price, .cost, [class*='price']")
|
||||||
|
addr_el = card.select_one(".address, .addr, [class*='address']")
|
||||||
|
link_el = card.select_one("a[href]")
|
||||||
|
href = link_el["href"] if link_el else ""
|
||||||
|
results.append(_item(
|
||||||
|
플랫폼="피터팬",
|
||||||
|
거래유형=trade_type,
|
||||||
|
가격표시=price_el.get_text(strip=True) if price_el else "",
|
||||||
|
주소=addr_el.get_text(strip=True) if addr_el else "",
|
||||||
|
건물명=title_el.get_text(strip=True) if title_el else "",
|
||||||
|
링크=self.BASE + href if href.startswith("/") else href,
|
||||||
|
))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"페이지{page} 오류({e}) ", end="")
|
||||||
|
break
|
||||||
|
|
||||||
|
print(f"{len(results)}건")
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
# ══════════════════════════════════════════════════════════
|
||||||
|
# 4. 네이버 부동산 크롤러
|
||||||
|
# ══════════════════════════════════════════════════════════
|
||||||
|
class NaverLandCrawler:
|
||||||
|
"""네이버 부동산 내부 API 기반 매물 수집 (법정동 코드 필요)"""
|
||||||
|
|
||||||
|
API = "https://new.land.naver.com/api"
|
||||||
|
|
||||||
|
TRADE_CODE = {"매매": "A1", "전세": "B1", "월세": "B2,B3"}
|
||||||
|
TYPE_CODE = {
|
||||||
|
"아파트": "APT", "오피스텔": "OPST",
|
||||||
|
"빌라": "VL", "원룸": "OR", "상가": "SG",
|
||||||
|
}
|
||||||
|
|
||||||
|
def fetch(self, cortar_no: str, real_estate_type: str, trade_type: str, max_pages: int = 5) -> list[dict]:
|
||||||
|
tag = f"[네이버부동산] {real_estate_type}/{trade_type}"
|
||||||
|
print(f" {tag} 수집 중...", end=" ", flush=True)
|
||||||
|
|
||||||
|
tc = self.TRADE_CODE.get(trade_type, "A1")
|
||||||
|
rc = self.TYPE_CODE.get(real_estate_type, "APT")
|
||||||
|
|
||||||
|
naver_headers = {
|
||||||
|
**HEADERS,
|
||||||
|
"Referer": "https://new.land.naver.com/",
|
||||||
|
"Accept": "*/*",
|
||||||
|
}
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for page in range(1, max_pages + 1):
|
||||||
|
try:
|
||||||
|
_delay()
|
||||||
|
r = requests.get(
|
||||||
|
f"{self.API}/articles",
|
||||||
|
params={
|
||||||
|
"cortarNo": cortar_no,
|
||||||
|
"order": "rank",
|
||||||
|
"realEstateType": rc,
|
||||||
|
"tradeType": tc,
|
||||||
|
"page": page,
|
||||||
|
"pageSize": 20,
|
||||||
|
},
|
||||||
|
headers=naver_headers,
|
||||||
|
timeout=15,
|
||||||
|
)
|
||||||
|
|
||||||
|
if r.status_code != 200:
|
||||||
|
if r.status_code == 403:
|
||||||
|
print(f"\n [네이버부동산] 봇 차단 감지 (403). 잠시 후 재시도하거나 VPN 사용 권장.")
|
||||||
|
break
|
||||||
|
|
||||||
|
data = r.json()
|
||||||
|
articles = data.get("articleList") or []
|
||||||
|
if not articles:
|
||||||
|
break
|
||||||
|
|
||||||
|
for art in articles:
|
||||||
|
ano = art.get("articleNo") or ""
|
||||||
|
dep_str = art.get("dealOrWarrantPrc") or art.get("warrantyPrice") or ""
|
||||||
|
rent_str = art.get("rentPrc") or ""
|
||||||
|
|
||||||
|
results.append(_item(
|
||||||
|
플랫폼="네이버부동산",
|
||||||
|
매물유형=art.get("realEstateTypeName") or real_estate_type,
|
||||||
|
거래유형=trade_type,
|
||||||
|
가격표시=dep_str + (f" / {rent_str}만" if rent_str else ""),
|
||||||
|
**{"면적(㎡)": art.get("area1") or ""},
|
||||||
|
층수=art.get("floorInfo") or "",
|
||||||
|
주소=art.get("cortarAddress") or "",
|
||||||
|
건물명=art.get("articleName") or "",
|
||||||
|
설명=(art.get("articleFeatureDesc") or "")[:80],
|
||||||
|
등록일=art.get("articleConfirmYmd") or "",
|
||||||
|
링크=f"https://new.land.naver.com/houses?articleNo={ano}" if ano else "",
|
||||||
|
))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"페이지{page} 오류({e}) ", end="")
|
||||||
|
break
|
||||||
|
|
||||||
|
print(f"{len(results)}건")
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
# ══════════════════════════════════════════════════════════
|
||||||
|
# Excel 내보내기
|
||||||
|
# ══════════════════════════════════════════════════════════
|
||||||
|
COL_WIDTHS = {
|
||||||
|
"플랫폼": 10, "매물유형": 10, "거래유형": 8,
|
||||||
|
"보증금(만원)": 14, "월세(만원)": 12, "가격표시": 22,
|
||||||
|
"면적(㎡)": 10, "층수": 8, "주소": 32, "건물명": 22,
|
||||||
|
"설명": 38, "등록일": 12, "링크": 55,
|
||||||
|
}
|
||||||
|
|
||||||
|
HEADER_FILL = PatternFill("solid", fgColor="1D4ED8")
|
||||||
|
HEADER_FONT = Font(color="FFFFFF", bold=True, size=10)
|
||||||
|
ALT_FILL = PatternFill("solid", fgColor="EFF6FF")
|
||||||
|
CELL_BORDER = Border(
|
||||||
|
left=Side(style="thin", color="CBD5E1"),
|
||||||
|
right=Side(style="thin", color="CBD5E1"),
|
||||||
|
top=Side(style="thin", color="CBD5E1"),
|
||||||
|
bottom=Side(style="thin", color="CBD5E1"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _style_sheet(ws) -> None:
|
||||||
|
for cell in ws[1]:
|
||||||
|
cell.fill = HEADER_FILL
|
||||||
|
cell.font = HEADER_FONT
|
||||||
|
cell.alignment = Alignment(horizontal="center", vertical="center")
|
||||||
|
cell.border = CELL_BORDER
|
||||||
|
|
||||||
|
for i, row in enumerate(ws.iter_rows(min_row=2), 2):
|
||||||
|
for cell in row:
|
||||||
|
if i % 2 == 0:
|
||||||
|
cell.fill = ALT_FILL
|
||||||
|
cell.border = CELL_BORDER
|
||||||
|
cell.alignment = Alignment(vertical="center", wrap_text=False)
|
||||||
|
|
||||||
|
for col_idx, col_name in enumerate(COLUMNS, 1):
|
||||||
|
ws.column_dimensions[get_column_letter(col_idx)].width = COL_WIDTHS.get(col_name, 15)
|
||||||
|
|
||||||
|
ws.row_dimensions[1].height = 22
|
||||||
|
ws.freeze_panes = "A2"
|
||||||
|
ws.auto_filter.ref = ws.dimensions
|
||||||
|
|
||||||
|
|
||||||
|
def export_excel(items: list[dict], filename: str) -> None:
|
||||||
|
if not items:
|
||||||
|
print("\n[경고] 수집된 데이터가 없습니다.")
|
||||||
|
return
|
||||||
|
|
||||||
|
df = pd.DataFrame(items, columns=COLUMNS)
|
||||||
|
|
||||||
|
with pd.ExcelWriter(filename, engine="openpyxl") as writer:
|
||||||
|
# 전체 시트
|
||||||
|
df.to_excel(writer, sheet_name="전체", index=False)
|
||||||
|
|
||||||
|
# 플랫폼별 시트
|
||||||
|
for platform in df["플랫폼"].unique():
|
||||||
|
sub = df[df["플랫폼"] == platform]
|
||||||
|
sub.to_excel(writer, sheet_name=platform[:31], index=False)
|
||||||
|
|
||||||
|
# 요약 시트
|
||||||
|
summary = (
|
||||||
|
df.groupby(["플랫폼", "거래유형", "매물유형"])
|
||||||
|
.size().reset_index(name="건수")
|
||||||
|
.sort_values("건수", ascending=False)
|
||||||
|
)
|
||||||
|
summary.to_excel(writer, sheet_name="요약", index=False)
|
||||||
|
|
||||||
|
# 스타일 적용
|
||||||
|
for ws in writer.book.worksheets:
|
||||||
|
_style_sheet(ws)
|
||||||
|
|
||||||
|
total = len(df)
|
||||||
|
platforms = df["플랫폼"].unique().tolist()
|
||||||
|
print(f"\n✅ 저장 완료: {filename}")
|
||||||
|
print(f" 총 {total}건 | 플랫폼: {', '.join(platforms)}")
|
||||||
|
|
||||||
|
|
||||||
|
# ══════════════════════════════════════════════════════════
|
||||||
|
# 메인 인터페이스
|
||||||
|
# ══════════════════════════════════════════════════════════
|
||||||
|
BANNER = """
|
||||||
|
╔══════════════════════════════════════════════════╗
|
||||||
|
║ 부동산 매물 크롤링 프로그램 v1.0 ║
|
||||||
|
║ 쟁승메이드 · jaengseung-made.vercel.app ║
|
||||||
|
╚══════════════════════════════════════════════════╝
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _choose(prompt: str, options: list[str]) -> str:
|
||||||
|
print(f"\n{prompt}")
|
||||||
|
for i, o in enumerate(options, 1):
|
||||||
|
print(f" {i}. {o}")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
n = int(input("▶ 선택: ").strip())
|
||||||
|
if 1 <= n <= len(options):
|
||||||
|
return options[n - 1]
|
||||||
|
except (ValueError, KeyboardInterrupt):
|
||||||
|
pass
|
||||||
|
print(" 올바른 번호를 입력하세요.")
|
||||||
|
|
||||||
|
|
||||||
|
def _choose_multi(prompt: str, options: list[str]) -> list[str]:
|
||||||
|
print(f"\n{prompt} (콤마 구분, 예: 1,3 / 전체: 0)")
|
||||||
|
for i, o in enumerate(options, 1):
|
||||||
|
print(f" {i}. {o}")
|
||||||
|
raw = input("▶ 선택: ").strip()
|
||||||
|
if raw == "0" or not raw:
|
||||||
|
return list(options)
|
||||||
|
chosen = []
|
||||||
|
for part in raw.split(","):
|
||||||
|
try:
|
||||||
|
n = int(part.strip())
|
||||||
|
if 1 <= n <= len(options):
|
||||||
|
chosen.append(options[n - 1])
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return chosen if chosen else list(options)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
print(BANNER)
|
||||||
|
|
||||||
|
# 1. 지역 입력
|
||||||
|
print("📍 검색할 지역을 입력하세요")
|
||||||
|
print(" 예: 강남구 / 마포구 / 홍대 / 수원 / 부산")
|
||||||
|
print(" (DB에 없는 지역은 위경도 직접 입력 가능)")
|
||||||
|
address = input("▶ 지역: ").strip() or "강남구"
|
||||||
|
|
||||||
|
coords = get_coords(address)
|
||||||
|
if not coords:
|
||||||
|
print(f"\n '{address}'이(가) 기본 DB에 없습니다.")
|
||||||
|
try:
|
||||||
|
lat = float(input(" 위도(lat) 입력 (예: 37.5172): ").strip())
|
||||||
|
lng = float(input(" 경도(lng) 입력 (예: 127.0473): ").strip())
|
||||||
|
coords = (lat, lng)
|
||||||
|
except ValueError:
|
||||||
|
print(" 좌표 입력 실패. 강남구 기본값으로 진행합니다.")
|
||||||
|
coords = (37.5172, 127.0473)
|
||||||
|
|
||||||
|
lat, lng = coords
|
||||||
|
print(f" → 위치: {lat:.4f}, {lng:.4f}")
|
||||||
|
|
||||||
|
# 2. 거래 유형
|
||||||
|
trade_type = _choose("💰 거래 유형", ["월세", "전세", "매매"])
|
||||||
|
|
||||||
|
# 3. 매물 유형
|
||||||
|
room_types = _choose_multi(
|
||||||
|
"🏠 매물 유형 (복수 선택 가능)",
|
||||||
|
["원룸", "투룸", "오피스텔", "아파트", "빌라"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4. 플랫폼 선택
|
||||||
|
platforms = _choose_multi(
|
||||||
|
"🌐 수집 플랫폼",
|
||||||
|
["직방", "다방", "피터팬", "네이버부동산"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# 네이버 부동산 법정동 코드 입력
|
||||||
|
cortar_no = ""
|
||||||
|
naver_types: list[str] = []
|
||||||
|
if "네이버부동산" in platforms:
|
||||||
|
print("\n [네이버부동산] 법정동 코드가 필요합니다.")
|
||||||
|
print(" 알려진 코드 목록:")
|
||||||
|
for name, code in list(CORTAR_CODES.items())[:8]:
|
||||||
|
print(f" {name:20s} : {code}")
|
||||||
|
cortar_no = input(" 법정동 코드 입력 (없으면 Enter 건너뜀): ").strip()
|
||||||
|
if cortar_no:
|
||||||
|
naver_types = [rt for rt in room_types if rt in NaverLandCrawler.TYPE_CODE]
|
||||||
|
if not naver_types:
|
||||||
|
print(" 선택한 매물 유형 중 네이버부동산 지원 유형 없음 — 건너뜀")
|
||||||
|
cortar_no = ""
|
||||||
|
|
||||||
|
# ── 수집 시작 ──────────────────────────────────────────
|
||||||
|
print(f"\n{'─'*50}")
|
||||||
|
print(f"🔍 수집 시작")
|
||||||
|
print(f" 지역: {address} | 거래: {trade_type}")
|
||||||
|
print(f" 매물: {', '.join(room_types)}")
|
||||||
|
print(f" 플랫폼: {', '.join(platforms)}")
|
||||||
|
print(f"{'─'*50}\n")
|
||||||
|
|
||||||
|
all_items: list[dict] = []
|
||||||
|
|
||||||
|
if "직방" in platforms:
|
||||||
|
zc = ZigbangCrawler()
|
||||||
|
for rt in room_types:
|
||||||
|
all_items.extend(zc.fetch(address, rt, trade_type))
|
||||||
|
|
||||||
|
if "다방" in platforms:
|
||||||
|
dc = DabangCrawler()
|
||||||
|
for rt in room_types:
|
||||||
|
all_items.extend(dc.fetch(lat, lng, rt, trade_type))
|
||||||
|
|
||||||
|
if "피터팬" in platforms:
|
||||||
|
pc = PeterpanzCrawler()
|
||||||
|
all_items.extend(pc.fetch(lat, lng, trade_type, max_pages=3))
|
||||||
|
|
||||||
|
if "네이버부동산" in platforms and cortar_no:
|
||||||
|
nc = NaverLandCrawler()
|
||||||
|
for rt in naver_types:
|
||||||
|
all_items.extend(nc.fetch(cortar_no, rt, trade_type))
|
||||||
|
|
||||||
|
# 중복 제거 (링크 기준)
|
||||||
|
seen: set[str] = set()
|
||||||
|
deduped: list[dict] = []
|
||||||
|
for item in all_items:
|
||||||
|
key = item.get("링크") or f"{item.get('주소')}{item.get('건물명')}{item.get('가격표시')}"
|
||||||
|
if key and key not in seen:
|
||||||
|
seen.add(key)
|
||||||
|
deduped.append(item)
|
||||||
|
|
||||||
|
print(f"\n{'─'*50}")
|
||||||
|
print(f"📊 수집 완료: {len(all_items)}건 → 중복 제거 후 {len(deduped)}건")
|
||||||
|
|
||||||
|
if not deduped:
|
||||||
|
print(" 수집된 매물이 없습니다. 지역·플랫폼 옵션을 변경해보세요.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 저장
|
||||||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M")
|
||||||
|
safe_addr = re.sub(r'[\\/:*?"<>|]', "_", address)
|
||||||
|
filename = f"부동산매물_{safe_addr}_{trade_type}_{timestamp}.xlsx"
|
||||||
|
|
||||||
|
print(f"💾 저장 중: {filename}")
|
||||||
|
export_excel(deduped, filename)
|
||||||
|
|
||||||
|
print(f"\n{'═'*50}")
|
||||||
|
print(" 프로그램 완료!")
|
||||||
|
print(f" 문의: bgg8988@gmail.com | 010-3907-1392")
|
||||||
|
print(f" 쟁승메이드: jaengseung-made.vercel.app")
|
||||||
|
print(f"{'═'*50}\n")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\n사용자가 프로그램을 종료했습니다.")
|
||||||
Reference in New Issue
Block a user