지난번에 Java 공부를 좀 한다고 했는데... 그 연습 코딩의 첫 결과물로 네이버 카페 중고 물품 탐색기를 만들어 보기로 했다.

 

네이버 카페에 물건을 검색하면, 대부분이 물건을 매입한다는 업자의 글이다. 한두번 올리는게 아니라 몇 분 단위로 도배를 하는데, 네이버와 중고나라는 이걸 제지할 생각이 전혀 없는 것 같다. 이 덕분에 중고나라의 이용자는 빠르게 번개장터와 당근마켓으로 넘어갔다. 

 

뭐 카페에서 특정 키워드가 포함된 게시물을 표시하지 않는 확장이 있기도 하지만, 카페 내 검색만 가능하고, 계속 다른 페이지를 넘겨 가면서 직접 물건을 찾는 건 귀찮은 일이다. 그래서 정해진 조건에 따라 물건을 알아서 찾아 주는 프로그램을 작성하기로 했다.

 

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Scanner;

public class NaverCafeUsedProductCrawler {
    public static void main(String[] args) {
        while (true) {
            ArrayList<String> wordBlackList = new ArrayList<>();
            String blackKeyword;
            Scanner sc = new Scanner(System.in);
            System.out.print("검색어를 입력하세요(나가시려면 Q 입력)>> ");
            String queryString = sc.nextLine();
            if (queryString.toUpperCase().equals("Q")) {
                break;
            }
            System.out.print("제외 키워드를 입력하세요(더 입력할 단어가 없으면 공백)>> ");
            while (true) {
                blackKeyword = sc.nextLine();
                if (blackKeyword.isEmpty()) { break; }
                wordBlackList.add(blackKeyword);
            }
            System.out.print("검색 페이지를 입력하세요(한 페이지에 10글) >> ");
            int pageNumbers = sc.nextInt();
            System.out.print("최대 가격을 입력하세요 >> ");
            int maximumPrice = sc.nextInt();
            System.out.println();
            try {
                Crawl(queryString, wordBlackList, pageNumbers, maximumPrice);
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("데이터를 가져오는 데 오류가 발생했습니다.");
            }
        }
    }
    public static void Crawl(String queryString, ArrayList<String> wordBlackList, int pageNumbers, int maximumPrice) throws IOException {
        Document doc;
        StringBuilder queryURLBuilder = new StringBuilder();
        queryURLBuilder.append("https://search.naver.com/search.naver?")
            .append("where=article&")
            .append("ie=utf8&")
            .append("query=").append(queryString.replace(" ", "+"))
            .append("&prdtype=4&")
            .append("t=0&")
            .append("st=date&")
            .append("date_option=0&")
            .append("date_from=&")
            .append("date_to=&")
            .append("srchby=text&")
            .append("dup_remove=1&")
            .append("cafe_url=&")
            .append("without_cafe_url=&")
            .append("board=&")
            .append("sm=tab_pge&")
            .append("nso=so:dd,p:all,a:all&")
            .append("start=");
        String queryURL = queryURLBuilder.toString();
        ArrayList<String> titleList = new ArrayList<>();
        for (int i=0; i<pageNumbers; i++) {
            doc = Jsoup.connect(queryURL + Integer.toString(i*10+1))
                    .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36")
                    .get();
            Elements elements = doc.select(".sh_cafe_top");
            for (Element element : elements) {
                int price = 0;
                try {
                    price = Integer.parseInt(element.select(".num").text().replace(",", ""));
                } catch (NumberFormatException e) {}
                Elements urlAndTitle = element.select("dl > dt > a");
                String url = urlAndTitle.attr("href");
                String title = urlAndTitle.text();
                if ((price <= maximumPrice) && (wordBlackList.stream().noneMatch(title::contains)) && (!titleList.contains(title))) {
                    System.out.println(String.format("제목 : %s", title));
                    System.out.println(String.format("링크 : %s", url));
                    System.out.println(String.format("가격 : %d", price));
                    System.out.println("----------------------------------");
                    titleList.add(title);
                }
            }
        }
    }
}

위는 코드다. 생각보다 간단한걸 볼 수 있다. 검색 세팅은 네이버 기본 검색, 카페, 최신순, 판매중 기준이다.

시험 삼아 맥북 프로 2015를 검색해 봤는데, 무려 2000글 중 1600글이 매입 글이었으며, 나머지 몇백 글은 같은 글이 중복된 것이었다.

 

mega.nz/file/CKZnBQyS#Ye6X1vrkN6W8ONU2k4ljRX6aq69nUHYrfPIwzx4_uxY

 

804.5 KB file on MEGA

 

mega.nz

.exe는 위 링크에서 다운받을 수 있다. 이걸 쓰기 위해서는 먼저 아래 링크에서 Java Runtime Environment를 설치해야 한다.

java.com/ko/download/

 

Download Java for Linux

사용자 컴퓨터용 Java 소프트웨어 또는 Java Runtime Environment는 Java Runtime, Runtime Environment, Runtime, JRE, Java Virtual Machine, Virtual Machine, Java VM, JVM, VM, Java 플러그인, Java 추가 기능 또는 Java 다운로드라고도

java.com

크롤링은 정말 재밌는 것 같다

'프로그래밍 > Java' 카테고리의 다른 글

네이버 카페 중고 물품 자동 탐색기  (0) 2020.10.05
Java 공부 시작  (0) 2020.08.14

이번에 정말 운이 좋게도 고작 글 15개로 애드센스 신청이 받아들여졌다. 

 

다른 블로그 보면 글자 수가 중요하다느니, 개수가 중요하다느니, 글을 규칙적으로 써야 한다느니 각종 말이 많지만 뭐 글자 수도 그닥이고 개수도 적고 게으르게 쓴 내가 받아들여진 것을 보면 꼭 그렇지는 않은 것 같다.

 

어쨌든 애드센스가 등록이 됐는데, 중요한 것은 티스토리 도메인으로 등록이 됐다는 것.

 

난 원래 내도메인.한국의 .n-e.kr 도메인을 사용하고 있었다. 근데 이 2차 도메인을 사용할 수가 없어서 티스토리로 신청을 했던 것이다.

 

그래서 어쨌든 방문자들을 이 티스토리로 유도하는 방법이 필요했는데, 내가 너무 섣불리 도메인 등록 해제를 해 버렸다.

 

그리고 내도메인.한국에 들어가서 리다이렉트를 이 곳으로 걸어 놨는데, 문제는 글 번호를 인식하지 못한다는 것.

 

만약 https://pagedown.n-e.kr/9 이런 식으로 된 url이 들어오면, 연결해주지를 못 했다.

 

근데 그 때 마침 발견한 것이 아래 글.

uxgjs.tistory.com/221

 

티스토리 2차 도메인 사용자들에게 안내 드립니다.

2020년 7월 24일날 티스토리에서 중요한 공지가 있었습니다. 2차 도메인 사용자들에 대한 유의사항으로 내용을 요약해 보면 크롬 및 메이져 브라우저의 보안정책으로 인해 2차 도메인 연결이 어렵

ux.stories.pe.kr

티스토리 스킨 편집에서 <head></head> 태그 사이에 스크립트를 넣어서 리다이렉트시키면 된다는 것이었다.

 

이거면 되겠다! 해서 다시 URL을 연결하려 했는데... 

 

n-e.kr을 기반으로 등록할 수 있는 도메인의 수를 초과하였습니다

 

아...

 

아...

 

이 때 딱 멘붕이 왔다.

 

그래서 급하게 방법을 찾았다. 지금 제일 중요한 것은 검색엔진 순위를 유지하는 것이었다.

 

근데? 구글 서치 콘솔에 주소 변경 기능이 있었다!!

 

서치 콘솔 > 설정에 들어가면 주소 변경 기능이 있었고, 이걸 활용해야 했다.

 

근데 어떡하지? 서치 콘솔의 주소 변경 조건은 먼저 기존 링크가 301 HTTP 상태 코드를 리턴해야 했고, 두 링크 모두 내 소유여야 했다. 뭐 두번째는 당연한 거고, 첫 번째가 문제였다.

 

문제는 301를 뱉어 줄 서버가 없다는 것

 

그럼 어떡하냐? 서버를 만들어야지.

 

방법은 이랬다.

 

기존 URL을 새로운 URL로 옮겨 주는 서버를 헤로쿠에 배포 > 기존 URL 연결 > 서치 콘솔에 주소 변경 설정

 

그럼 한번 해 보자.

 

일단 서버 코드를 작성해야 한다. 다행히 Flask를 위한 기초적인 뼈대는 인터넷에 다른 블로거분이 작성해 주셨다.

 

*이 방법은 프로그래밍과 heroku 사용법을 어느 정도 알고 있다는 가정 하에 작성됐습니다

 

awayday.github.io/2017-06-10/heroku-with-python-flask/

 

Heroku에 Flask 앱 배포하기

heroku는 다 간편한데 설정이 귀찮고 설명이 불친절하다(지극히 주관적)

awayday.github.io

여기 Github에서 배포되는 코드 중에서 prototype/__init__.py를 보자.

 

from flask import Flask
from flask import render_template

app = Flask(__name__)


@app.route("/")
def intro():
    return render_template('index.html')

이렇게 돼있을 것이다. 코드를 아래와 같이 고치면 된다 (URL은 각자 해당하는 것으로 고쳐라).

from flask import Flask
from flask import redirect

app = Flask(__name__)


@app.route("/")
def intro():
    return redirect("https://pagedown.tistory.com/", 301)

@app.route("/<int:a_n>")
def a(a_n):
    return redirect("https://pagedown.tistory.com/" + str(a_n), 301)

if __name__ == "__main__":
    app.run()

 위 intro는 루트 도메인을 원래 페이지로 연결하면서 301 코드를 응답해주는 것이고, 아래는 다른 도메인 주소(숫자만 된다. 문자 형식이면 아래 참고)를 티스토리 원래 페이지로 연결해 주는 것이다.

from flask import Flask
from flask import redirect

app = Flask(__name__)


@app.route("/")
def intro():
    return redirect("https://pagedown.tistory.com/", 301)

@app.route("/<str:a_a>")
def a(a_a):
    return redirect("https://pagedown.tistory.com/" + a_a, 301)

if __name__ == "__main__":
    app.run()

이 코드는 주소가 문자일 경우 연결해 주는 코드이다.

그리고 우리는 아직 해줘야 할게 하나 남았다.

requirments.txt를 수정해 줘야 한다.

click
Flask
gunicorn
itsdangerous
Jinja2
MarkupSafe
Werkzeug

이렇게 수정해 주면 된다. 원래 저기에 ==버전 이렇게 되어 있을 텐데 바꿔준 이유는 저걸 그대로 배포하면 오류가 났기 때문이다. 정확한 이유는 나도 잘 모르겠다.

 

그 다음에 이제 차근 차근 따라 오시라

 

먼저 깃 커밋을 해줘야 한다.

> git add .
> git commit -m "commit message"

위와 같이 입력해 준다.

 

그 다음 배포를 해줘야 한다. 배포는 아래와 같이 한다 (뭐 다들 아시겠지만).

git push heroku master

그리고부터가 좀 어렵다. 먼저 URL을 연결해 줘야 한다. 아래 링크에 따라 URL을 연결해 주자.

velog.io/@moonseok/Heroku-%EB%AC%B4%EB%A3%8C-%EB%8F%84%EB%A9%94%EC%9D%B8-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0

 

Heroku 무료 도메인 설정하기

공짜의 공짜의 공짜

velog.io

연결이 됐다면 이제 SSL을 추가해야 한다. 근데 이것은 헤로쿠 hobby 플랜 이상 가입된 사람만 가능하다. 즉 7달러를 결제하거나 깃허브 학생계정에 가입되어 있어야 한다는 뜻. 난 다행히 깃허브 학생계정에 가입되어 있어서 할 수 있었다.

 

SSL을 등록하는 방법은 아래와 같다.

> heroku ps:resize web=hobby
> heroku certs:auto:enable

이러면 알아서 해 준다. 이제 기나긴 여정은 끝났다. 구글 서치 콘솔에서 주소 변경을 해 주자. 반영 시간이 좀 걸릴 수 있기 때문에 기다린 다음 해 주면 알아서 순위 변동 없이 주소를 바꿔 준다.

 

이번에 설명을 너무 급하게 써서 생략된 부분도 많고 정신없을 수 있다. 일단 이 방법 자체가 많이 복잡하고, 아무것도 모르는 일반인이 시작하기엔 Python 인터프리터와 heroku CLI, git 설치부터가 고난일 것 같아 프로그래밍을 어느 정도 아시는 분들이 시도해 보시기 바란다. 그리고 웬만하면 위에 설명된 JS를 이용한 리다이렉트 방법을 쓰는 것이 좋을 것 같다.

'잡담' 카테고리의 다른 글

티스토리 도메인 옮기는 법  (0) 2020.10.01
블로그 방문자 수 10000명 돌파  (0) 2020.08.27

계속 인터파크 티켓팅 매크로에서 회차 인식이 제대로 되지 않는다는 댓글이 많다. 다시 보니까, 회차가 그날 그날 1부터 시작하는게 아니라, 한 시점을 기준으로 계속 더해지는 거였다. 따라서 이걸 인식하는 기능을 구현하고는 있으나, 생각보다 잘 안되서 방법을 제시하고자 한다. 다만 이 방법은 좀 어려우니 잘 따라 오시길 바란다.

 

일단 웹 페이지의 HTML을 분석하여 회차를 가져와야 한다. 방법은 다음과 같다.

 

이 공연을 예시로 들겠다.

ticket.interpark.com/Ticket/Goods/GoodsInfo.asp?GoodsCode=20007444

 

싸니까 믿으니까 인터파크 티켓

 

ticket.interpark.com

크롬으로 접속한 후, F12를 누른다. 그러면 오른쪽에 아래와 같은 창이 뜰 것이다.

여기서 우리는 회차를 찾아야 한다. 따라서 공연을 들을 날짜를 달력에서 선택한다.

17일을 선택했다. 그러면 아래에 회차를 선택할 란이 있을 것이다.

회차를 선택했다.

 

그럼 이제 요소를 찾아서 회차를 알아내야 한다. 방법은 바로 개발자 도구 맨 왼쪽 위에 있는 버튼을 누르는 것이다.

저 버튼을 누른다. 그러고 웹 페이지 위에 마우스를 올리면 뭔가 다를 것이다.

회차를 눌러 준다. 그러면 오른쪽 소스 코드 창에 아래와 같이 쓴다.

ul 요소 왼쪽의 오른쪽으로 향한 삼각형을 눌러준다. 그러면 아래와 같이 된다. 그 안에서 또 li 요소 왼쪽의 오른쪽으로 향한 삼각형을 모두 눌러준다.

여기서 우리는 18시 00분 회차를 원했으므로 그 요소를 보자.

우리가 원하는 회차는 여기 있는 것 같다. 저 input 요소의 value를 회차에 적어 주면 된다. 여기서는 002이지만 이건 공연에 따라 다르므로 이 방법을 적용해서 찾아보시기 바란다.

 

이 방법은 조금 어려울 수 있으나, 저 회차를 찾는 기능 구현이 생각보다 난해해서 쉽지가 않다. 공연 전에 정보는 미리 나오므로 여유롭게 회차를 알아 두시면 될 것이다.

'프로그래밍' 카테고리의 다른 글

인터파크 티켓팅 매크로 회차 관련 안내  (0) 2020.09.24

+ Recent posts