0. 시작하기 전 잡소리

교외근로 하는 곳에서 특정 사이트에서 (특정 인물의) 사진을 긁어달라는 요청을 받았다.
사실 필요한 수가 적으면 손으로 일일히 해도 되지 싶었는데, 중복을 제외하고 5천장이 필요하다는 말씀을 하시더라
이걸 맨손으로 직접 긁다가는 정신 놓겠다는 생각에 일단 헤딩해보기로 햇따



1. 어떻게 긁어올까 (1)

남들이 만들어둔 파이썬 크롤러를 가져다가 쓰면 되지 않을까라는 막연한 생각으로 검색을 시작했다.
icrawler가 구글 검색 결과랑 그 외에 몇 가지를 더 지원하길래 이걸로 구글 검색 결과를 다 긁어오면 끝일줄 알았는데....
구글에서 최대 검색 결과를 1000개로 제한해서 한 번에는 5천개를 채울 수 없었다. 이외에도 인물 사진이 아니라 특정 인물이 생전에 사용하던 빗이나 장화 같은게 같이 잡혀버리는 문제도 있었고...


장화는 우리한테 필요가 없다...


사실 1천개 제한에 걸리기 이전에, 검색에서 잡히는 이미지가 900개 남짓이라서, 5천개를 전부 구하려면 결국 사이트에서 직접 긁어와야 했다. 



다행히도, 이미지를 긁어달라고 요청 받은 사이트는 사진 자료들만 따로 모아서 볼 수 있는 페이지를 제공했다.




icrawler에 내장된 GreedyImageCrawler가 사이트에 img 태그가 달린 모든 파일을 뽑아내서, 해상도 상 / 하한선을 정해 크롤링할 수 있어서 이걸 그대로 쓰려고 했으나...
이렇게 하면 해상도가 낮은 썸네일용 이미지를 긁어오게 되는 문제도 있고, 사료 페이지 하나에 이미지가 여러 개 등록된 글의 이미지를 올바르게 긁어오지 못한다는 문제가 있었다. 




2. 어떻게 긁어올까 (2)

사진 자료들이 등록된 게시글 번호가 연속적이었으면 시작과 끝만 찾는 걸로 해결될 일이었지만, 2천번대, 4천번대, 2백만 등 연속적이지 않았기에 사진 자료가 있는 게시글 번호만 알아낼 방법이 필요했다.
사진 자료를 조회하는 페이지에서 각 페이지의 제목이 연결하는 링크를 긁어내고, 그 링크로 들어가서 본문에 이미지가 있다면 그 이미지 주소를 다 긁어낸 다음, icrawler한테 주면 얘가 알아서 긁겠지 라는 생각으로 BeautifulSoup를 이용해보기로 했다.


import requests
from bs4 import BeautifulSoup

f = open("url_list.txt", 'a')
target_url = 'http://archives.knowhow.or.kr/record/image/list/'
dest_url = 'http://archives.knowhow.or.kr'

for page_number in range(1, 1377):
    req = requests.get(target_url + str(page_number))
    html = req.text
    soup = BeautifulSoup(html, 'html.parser')

    my_urls = soup.select(
        'li > p:nth-child(2) > span > a'
    )
    for url in my_urls:
        data = dest_url + url.get('href') + "\n"
        f.write(data)

f.close()


글 제목이나 썸네일 사진, 본문 중 a 태그의 href 속성이 각 사료로 향하는 주소를 담고 있다. BeautifulSoap를 이용해, 각 페이지에서 글 제목의 a 태그를 뽑아내고, href를 온전한 URL로 만들어서 텍스트 파일로 저장한다.


import requests
from bs4 import BeautifulSoup

f = open("url_list.txt", 'r')
f2 = open("file_list.txt", 'a')

while True:
    line = f.readline()
    if not line: break
    
    req = requests.get(line)
    html = req.text
    soup = BeautifulSoup(html, 'html.parser')

    file_number = soup.select(
        '.tomatoGallery > tbody > tr > td > img'
    )

    for file in file_number:
        data = file['src'] + "\n"
        f2.write(data)

f.close()
f2.close()



만들어진 URL로 사료 보기 페이지에 접속하면, 한 장 또는 여러 장의 사진이 페이지에 있는데, 썸네일 사진이 아닌 본문 사진들의 img 태그를 뽑아내어, src를 알아내어 각 사진 파일의 주소를 얻는다.


from icrawler.builtin import UrlListCrawler urllist_crawler = UrlListCrawler(downloader_threads=8, storage={'root_dir': 'D:\iCrawler'}) urllist_crawler.crawl('file_list.txt', max_num=17936)


icrawler를 통해 사진 파일의 주소에 있는 사진 파일을 다운받는다.

(max_num = 17936은 file_list.txt에 있는 파일의 개수를 다운로드 받을 파일 개수의 상한선으로 지정한 것이다.)





구도가 비슷한 사진을  추려내고도, 5천장보다 훨씬 많은 사진을 확보할 수 있었다.

'etc' 카테고리의 다른 글

코드 하이라이팅 테스트  (0) 2018.10.03

문제 링크: https://www.acmicpc.net/problem/2493


주어진 조건을 요약해보면, 어떤 탑은 그 왼쪽에 자신보다 큰 탑이 있어야 발사한 레이저 신호를 송신하는 탑이 존재한다는 것입니다.

스택과 pair를 활용하여, 탑의 위치와 높이를 저장하는 식으로 해결했습니다.


우선 입력이 들어왔을 때 스택이 비어있다면, 그 탑의 레이저를 송신할 대상이 없으므로 0을 출력하고 탑의 위치와 높이를 스택에 저장합니다.


스택이 비어있지 않을 때 입력이 들어오면, 입력된 탑의 높이와 스택에 저장된 탑의 높이를 비교합니다.


입력된 탑의 높이가 더 높으면, 기존의 탑을 스택에서 제거하고, 새 탑을 스택에 저장해둡니다.


입력된 탑의 높이가 스택에 비해 같거나 낮으면, 입력된 탑이 송신한 레이저는 스택의 탑이 수신하게 됩니다.

스택에 저장된 탑의 정보를 출력하고 다음 탑의 정보를 입력받도록 하면 됩니다.



문제에 주어진 예제 6, 9, 5, 7, 4로 설명해보면,


처음 탑은 pair<1, 6>으로 표현이 가능합니다. 왼쪽에 그 어떤 탑도 없으므로 레이저를 수신하는 탑이 없습니다. 0을 출력하고 <1, 6>을 스택에 넣습니다.


9를 입력받고, 스택에 저장된 탑의 높이 6과 비교합니다. 9가 더 크므로 두 번째 탑의 레이저도 수신할 수 있는 탑이 없습니다.

0을 출력하고 <1, 6>을 스택에서 꺼낸 다음 <2, 9>를 넣어둡니다.


5가 입력되면 9와 5를 비교하여 5가 더 작으므로, <3, 5> 탑의 레이저를 <2, 9> 탑이 수신하게 됩니다.

수신하는 탑의 번호 2를 출력하고 다음 입력을 받습니다.


같은 방법으로 <4, 7> 탑의 정보도 <2, 9> 탑이 수신하게 됩니다.


4를 입력받았을 때, 7이 4보다 크므로 스택에 <4, 7>을 저장합니다.


입력이 끝날 때, 마지막 탑의 레이저를 수신하는 탑의 번호(=스택에 저장된 탑)를 출력하고 종료합니다.




C++

#include <cstdio>
#include <stack>
#include <utility>
using namespace std;

int main() {
    int num, input;
    stack<pair<int, int> > st;

    scanf("%d", &num);
    for(int i = 0; i < num; i++) {
        scanf("%d", &input);

        while(!st.empty()) {
            if(st.top().second > input) {
                printf("%d ", st.top().first);
                break;
            }
            
            st.pop();
        }

        if(st.empty()) printf("0 ");
        st.push(make_pair(i + 1, input));
    }

    return 0;
}


원래 cin, cout을 사용하여 작성하였는데 채점할 때 시간 초과가 나와서 printf, scanf를 사용하는 방식으로 실행 시간을 줄여서 해결했습니다.

'Algorithm > BOJ' 카테고리의 다른 글

BOJ 9935번: 문자열 폭발  (0) 2018.10.03
BOJ 9012번: 괄호  (0) 2018.10.03
BOJ 10799번: 쇠막대기  (0) 2018.10.03
BOJ 1001번: A-B  (0) 2018.10.03
BOJ 1000번: A+B  (0) 2018.10.03

문제 링크: https://www.acmicpc.net/problem/9935


문제 분류에는 스택이라고 표기되어 있지만, 꼭 쓰지 않아도 될 것 같아서 스택 없이 풀어보았습니다.


입력 받은 문자열을 우선 한 글자씩 결과를 저장할 문자열에 옮깁니다.

계속 옮겨주다가, 폭발 문자열의 마지막 글자와 같은 문자를 발견하면 비교를 시작합니다.

결과를 저장하는 문자열에 폭발 문자열이 있다면, idx를 줄여서 폭발 문자열 위에 입력 받은 문자열을 덮어씁니다.

원래 문자열이 끝나면, 결과 문자열의 끝에 NULL 캐릭터를 삽입합니다.

문제의 조건대로, 전부 폭발한 다음 남은 문자열이 없으면 (idx가 0이면) FRULA를 출력합니다.


C++

#include <iostream>
#include <string>
using namespace std;

int main() {
    char result[1000001]; // 결과를 저장할 char 배열 
    string input, tgt;
    int tgtLen, idx = 0;

    cin >> input >> tgt;
    tgtLen = tgt.length();

    for (int i = 0; i < input.length(); ++i) {
        result[idx++] = input[i]; // 입력된 문자열을 한 글자씩 옮김

        if (result[idx - 1] == tgt[tgtLen - 1]) { // 마지막으로 입력한 글자가 tgt의 끝 글자와 같으면 
            if (idx - tgtLen < 0) continue; // 예외 처리; result가 tgt보다 짧으면 검사할 필요 없음 
            bool hasTGT = true;

            for (int j = 0; j < tgtLen; ++j) {
                // idx 앞 쪽의 문자열이 tgt와 같은지 검사 
                if (result[idx - 1 - j] != tgt[tgtLen - 1 - j]) {
                    hasTGT = false;
                    break;
                }
            }
            if (hasTGT) idx -= tgtLen; // tgt가 존재하면 idx를 줄임 
        }
    }
    result[idx] = '\0'; // 결과의 끝에 NULL 문자 삽입. 
    idx == 0? printf("FRULA"): printf("%s", result);

    return 0;
}

'Algorithm > BOJ' 카테고리의 다른 글

BOJ 2493번: 탑  (0) 2018.10.03
BOJ 9012번: 괄호  (0) 2018.10.03
BOJ 10799번: 쇠막대기  (0) 2018.10.03
BOJ 1001번: A-B  (0) 2018.10.03
BOJ 1000번: A+B  (0) 2018.10.03

+ Recent posts