g2g-crawling06: div.noresult-main-title

div.noresult-main-title 업데이트(20220304)

크롤링하는 페이지에 새로운 문제가 발생했다.

div.noresult-main-title 태그로 인해 server랑 currency 값을 불러올 수 없었다.

noresult

URL에서 가져올 수 있겠지만 우선 함수를 빠져나가는 방식으로 업데이트 했다.

1
2
3
4
5
6
def get_data():
...

if (soup.find("div", class_="noresult-main-title").string == "The offer you try to view is no longer available."):
driver.close()
return False

if return을 추가하니 해당 페이지가 정상 title로 복구됐다… 작동 확인도 못해봤다.

g2g-crawling05: 엑세스 키 관리 문제

엑세스키 노출

파일 하나에서 함수를 모두 관리했었고, 당연 문제가 됐다.

이전 업데이트 때 깜빡하고 GCP acc 키가 제거되지 않은 상태로 push 해버린 것.

조취(20220303)

당장 연결 권한을 바꿔야한다.
그 이후, 같은 실수가 발생하지 않도록 관리방법이 추가되야한다.

불특정된 사람들이 깃헙을 유심히 볼만한 정도는 아니라 다행이라 생각한다.

엑세스키 제거 후 재발급

commit을 일일히 삭제하는 방법을 생각했지만, 어딘가엔 데이터가 남을 가능성이 있어 GCP에서 서비스 키를 새로 발급받았다.

이걸로 일단 노출된 키로 접근하는 불상사는 막았다.

새로운 py 파일 생성

source를 계속 업데이트 한다면 역시 까먹어도 문제 없도록 엑세스 함수를 숨기거나 따로 관리해야한다.

기존에 사용하던 crawl_URL_boost.py에서 SQL에 접근하는 데이터가 들어있는 connecting, dataInsertPsycopg2, accGBQ을 crawl_acc_sql.py 파일로 옮겨 개인 모듈로 관리한다.

crawl_acc_sql.py에 사용되는 모듈들을 boost에서 옮겨담는다.

crawl_acc_sql.py
1
2
3
4
5
import pandas_gbq
import psycopg2
from psycopg2 import connect, extensions
import psycopg2.extras as extras
from google.oauth2 import service_account

crawl_URL_boost.py에는 crawl_acc_sql 모듈을 추가함에 따라 기존 함수들도 모듈에 맞춰 수정했다.

crawl_URL_boost.py
1
2
3
4
5
6
7
8
9
import crawl_acc_sql as accsql


if __name__ == "__main__":

conn = accsql.connecting()
accsql.dataInsertPsycopg2(conn, data=df)

accsql.accGBQ(df)

혹시 모를 에러를 대비해 .backup 로컬 폴더에 수정된 날짜를 파일명에 추가해 이전 버전을 백업했다.

새로운 sql을 추가하지 않는 이상 crawl_acc_sql.py를 편집할 일은 없으므로 계정 키를 항상 지워서 업로드해야하는 불편함이 줄었다.

내부링크

g2g-crawling04: 동적페이지 로드 문제 발생

동적 페이지 자동 크롤링을 새벽마다 돌리면서 URL이 변경되지 않는 동작의 경우 요소를 찾을 때 까지 대기하는 코드가 때때로 에러를 일으킨다.

다음에 error가 식별됐을 때 Error 명을 식별해서 try - except 구문으로 예외처리하고 time.sleep 함수를 넣어준 다음 try 재귀 함수를 돌리는 코드를 추가 작성해 업데이트할 예정이다.

try & 재귀함수 추가(20220228)

get_data() 함수에 try - except로 구성된 재귀함수 try_clickable()을 추가했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def get_data():

def try_clickable(xpath_name):
try:
i = 0
soup = soup_wait_clickable(xpath_name)

seller.extend(get_data_seller(
"a.flex.prechekout-non-produdct-details > div.seller-details.m-l-sm > div.seller__name-detail"))
price.extend(get_data_seller(
"div.hide > div > div > div > div > div > span.offer-price-amount"))
stock.extend(get_data_stock())
except IndexError as e:
i += 1
print(e+str(i))
time.sleep(1)
soup = try_clickable(xpath_name)
return soup

클릭 동작이 들어간 soup_wait_clickable 부분만 업데이트 했지만, 만약 클릭 동작이 필요없는 페이지에서도 IndexError가 발생한다면 앞부분에도 추가해야한다.

getdata[함수] 요소 추가(20220304)

다른 문제로 데이터를 재확인하다가 중복 데이터가 지난 업데이트인 2월 28일부터 들어있음을 확인했다.

1
SELECT * FROM poe where date = '2022-03-04' order by currency, server, seller;

으악 아니야

일단 당장 새로 갈아끼울 수 있는 3/4 데이터만이라도 삭제한다

1
2
3
4
5
--PostgreSQL
delete from poe where date = '2022-03-04';

--GBQ
delete from DB_G2G.poe where date = '2022-03-04';

ㅈ됨. 나중에 데이터를 쓸 때 2/27이나 3/4 데이터를 사용해서 어떻게든 처리해야겠지

처음 문제는 time.sleep() 값이 부족한줄 알고 4까지 넣어봤지만 여전히 중복 데이터가 들어갔다.
아무래도 get_data에서 처리하는 soup 변수가 문제있는거같다.
28일에 백업해둔 코드를 실행해보고 문제가 발생하지 않아 확신했다.

try 함수를 넣으면서 soup의 위치가 get_data.try_clickable()로 들어감..
기존 soup는 get_data()에 위치했으며, get_data.get_data_seller(selector) or get_data.get_data_stock()에 적용 시 문제 없이 get_data의 soup로 들어갔다.

하지만 이제 내가 넣어야하는 soup는 get_data.try_clickable() 아래에 있는 soup를 넣어야했다.

따라서 getdata 함수를 아래와 같이 요소를 추가했다.

1
2
3
4
5
def get_data():
def get_data_seller(soup,selector):
...
def get_data_stock(soup):
...

마찬가지로 getdata 함수를 사용하는 구문에도 soup 요소를 추가했다.

2022-02-28 ~ 2022-03-03 오또케…

외부링크

pandas_gbq python과 google bigquery 연동하기

GCP 엑세스키 발급받기를 선행

라이브러리 설치

google-cloud-bigquery

python과 google bigquery를 연동하기 위한 라이브러리

1
$ pip install google-cloud-bigquery

pandas-gbq

dataframe을 gbq에 업로드하기 위한 라이브러리

1
$ pip install pandas-gbq

GBQ 연동

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from google.oauth2 import service_account

## json 파일 내용
credentials = service_account.Credentials.from_service_account_info(
{
"type": "",
"project_id": "",
"private_key_id": "",
"private_key": "",
"client_email": "",
"client_id": "",
"auth_uri": "",
"token_uri": "",
"auth_provider_x509_cert_url": "",
"client_x509_cert_url": ""
}
)

## json 파일 경로를 사용하고 싶으면 `from_service_account_file` 함수를 사용하면된다
credentials = service_account.Credentials.from_service_account_file("URL.json")

업로드 하고싶은 dataframe(df)을 만들어두자.

pandas.to_gbq로 연동하면 브라우저 창을 통해 쿠키를 받아야하므로 pandas_gbq를 사용한다.

1
2
3
4
5
6
7
import pandas_gbq

project_id = "project_id"
table_name = "data_set_name.table_name"

pandas_gbq.context.credentials = credentials
pandas_gbq.context.project = project_id

dataframe 업로드

if_exists 요소의 기본값 = “fail”

  • fail: 테이블이 존재하면 pandas_gbq.gbq.TableCreationError 발생
  • replace: 테이블이 있는 경우 다시 만들고 데이터 삽입
  • append: 테이블에 데이터 삽입(테이블이 없는 경우 테이블 생성)
1
pandas_gbq.to_gbq(df, table_name, project_id=project_id, if_exists="append")

bigquery data 받아오기

1
2
3
4
import pandas as pd

query = "SELECT * FROM `data_set_name.table_name`"
readdf = pd.read_gbq(query=query, project_id=project_id, credentials=credentials, dialect='standard')

외부링크

pyspark 설치

pyspark

대용량 data를 관리하기 위한 유사 SQL 라이브러리

사전 준비

  • python 설치
  • java 설치
  • spark 다운로드
  • winutils 다운로드

python 설치

파이썬 혹은 아나콘다를 설치한다.
python 버전 3 이상으로 설치한다.

java 설치

나의 경우 java가 설치되어있고 JAVA_HOME 환경변수까지 설정되있기에 그대로 사용했으나 아니라면 오라클에 로그인하고 java를 설치한다.

이후 JAVA_HOME 환경 변수를 추가해준다.

JAVA_HOME

시스템 변수, 사용자 변수 중 원하는 영역에 추가해준다.
난 여러 계정을 사용하지 않으니 그냥 사용자 변수에 넣어줬다.

Spark 다운로드

Spark tgz 압축파일을 다운받는다.

SPARK

원하는 경로에 압축 풀고 위와 동일하게 환경 변수를 설정해준다.

SPARK-경로
SPARK_HOME

winutils 다운로드

위에서 받은 spark 버전과 동일 버전의 winutils.exe를 받아준다.

winutils용 폴더를 만들고 bin 파일 아래에 넣어준다.

winutils-경로

hadoop 환경변수도 설정한다.

HADOOP_HOME

Path 설정

마지막으로 path 값에

  • %JAVA_HOME%\bin
  • %SPARK_HOME%\bin
  • %HADOOP_HOME%\bin
    을 넣어준다.

path

pyspark 실행

CMD(혹은 Anaconda Prompt)를 열어서 pyspark 설치한다.

1
> pip install pyspark

1
> pyspark

pyspark

외부링크

unique 요소만 포함하는 set

특정 집합을 만들다보니 같은 값이 저장되지 않는 리스트를 만들 필요가 생겼다.
set() 형식을 사용하면 unique한 객체만 포함하는 집합을 만들 수 있다.

단, 사용하려면 set()으로 객체를 초기화 해야한다.

1
2
set = set([1,1,1,1,2,3,10,10,31])
print(set)
{1, 2, 3, 10, 31}

dictionary와 동일한 {, }로 사용되지만 dictionary와 다르게 key가 없는 list 형식이다.

외부링크

g2g-crawling03: G2G poe 현거래 매물 수집시작

저번 토요일(02/05) poe 리그 시작일에 맞춰 한국시간 06시마다 매일 현거래 매물 데이터를 postSQL과 GBQ에 수집중이다.

postSQL

pandas_gbq python과 google bigquery 연동하기

GBQ

2, 3 시즌 이상 데이터가 쌓이면 반복되는 데이터로 머신 러닝을 돌려 유저가 빠지는(혹은 매물이 폭락하는) 기간을 예상할 수 있을 것이다.

그 전에 google data studio를 이용해서 대쉬보드도 관리할 예정이다.

datetime 날짜와 시간

datatime

datetime 모듈은 특정 시간과 날짜를 불러오는 파이썬 내장 모듈이다.

날짜와 시간 함수

datatime 모듈을 사용하면 시간 날짜 UTC timezone을 불러올 수 있고, 내장 함수로 data, time 등을 사용해 날짜 혹은 시간만 지정하고 특정 시간대를 지정해 불러올 수도 있다.

1
2
3
4
5
import datetime

print(datetime.date.today())
print(datetime.datetime.today())
print(datetime.time(12,59,33, microsecond=333333, tzinfo=datetime.timezone.utc))

2022-02-03
2022-02-03 03:36:07.407621
12:59:33.333333+00:00

strftime(원하는 형식으로 변경)

strftime을 사용하면 불러온 시간을 원하는 형식으로 바꿀 수 있다.

1
print(datetime.datetime.today().strftime("%Y/%m/%d %H:%M:%S"))

2022/02/03 03:40:36

외부링크

time 모듈로 일시정지

python 내장 함수인 time 모듈을 사용하면 시간 관련 추출이나 간섭을 할 수 있다.

그 중 이번에 내가 사용할 함수는 sleep() 함수로 지정한 시간 단위만큼 프로그램 실행 중간에 딜레이를 줄 수 있다.

특히 이번에 크롤링 중인 페이지가 동적페이지에 하나의 URL에서 클릭으로 HTML 코드만 바뀌는 옵션이 들어가 selenium만으로 크롤링하면 때때로 에러가 발생했다.

1
2
3
import time

time.sleep(3)

을 사용하면 코드 중간에 3초간 딜레이 줄 수 있다.

외부링크

BeautifulSoup find와 select

request 혹은 selenium으로 html 코드를 뜯어왔다면 태그를 특정지어서 불러올 데이터를 지정해줘야한다.
이 때, 사용할 수 있는 모듈 중 하나가 BeautifulSoup와 Scrapy다.

BeautifulSoup

bs4를 설치하고 BeautifulSoup를 사용한다.

1
$ pip install beautifulsoup4
1
2
3
from bs4 import BeautifulSoup

soup = BeautifulSoup(html)

기본적으로 태그를 식별하는 방법은 findselect가 있다.

find

find의 경우 tagid, class 등을 지정해서 찾을 수 있다.

python에서는 class가 따로 쓰이므로 class 요소를 지정할 때는 class_로 넣어줘야한다.

1
2
3
4
soup.find("strong")
soup.find("a", class_="cdp_i")
soup.find(class_="cdp_i")
soup.find("div", id="hide")

class와 id같은 요소를 지정해서 불러오기 때문에 특정 상황에선 정확도가 높다.

find_all

find()의 경우 하나의 태그 html만 불러올 수 있는대, find_all()을 사용하면 조건을 만족하는 모든 html을 list 형식으로 받아온다.

1
soup.find_all("a")

select

select를 사용하면 find_all()처럼 list로 받아올 수 있다.

1
2
3
4
soup.select("a")
soup.select("a.cdp_i")
soup.select(".cdp_i")
soup.select("div#hide")

또한, 중첩 tag를 선택할 수도 있다.

1
soup.select("div#hide > a.cdp_i")

div 태그의 id=”hide” 내부의 a 태그의 class=”cdp_i”인 html을 불러온다.

select의 경우 메모리 소모량과 수행시간이 find와 비교하면 효율적이다.

select_one

find의 경우와 반대로 가장 앞의 하나만 불러오기 위해선 select_one을 사용하면된다.

1
soup.select_one("a")

외부링크