import os import asyncio from playwright.async_api import async_playwright from datetime import datetime
# 현재 실행 디렉토리를 가져온다. current_dir = os.getcwd() # 디렉토리의 맨 마지막에 / 구분자의 존재 여부를 확인하여, 항상 / 로 끝나도록 만든다. if current_dir[-1] != '/': current_dir = current_dir + '/' # 현재 실행 디렉토리를 출력한다. #print(current_dir)
# 비동기 실행 메인 함수 async def main(): # 현재 날짜와 시간을 얻어오고, 년월일 시분초 형식으로 구성한다. current_datetime = datetime.now().strftime("%Y-%m-%d %H-%M-%S") #print("Current date & time : ", current_datetime) # 년월일 시분초 형식으로 문자열을 생성한다. str_current_datetime = str(current_datetime)
# 비동기 playwright 객체를 가지고 실행 async with async_playwright() as p: browser = await p.chromium.launch(headless=True) # 브라우저 생성 page = await browser.new_page() # 브라우저의 페이지 생성
""" 블로그에 있는 글의 제목들을 추출하는 xpath 구문 page.locator("//div[@class='entry']/div[@class='titleWrap']/h2/a").all_text_contents()
블로그에 있는 글의 작성일자를 추출하는 xpath 구문 page.locator("//div[contains(@class,'entry')]/div[@class='titleWrap']/div[@class='info']/span[@class='date']").first.text_content() page.locator("//div[@class='entry']/div[@class='titleWrap']/div[@class='info']/span[@class='date']").last.text_content() page.locator("//div[@class='entry']/div[@class='titleWrap']/div[@class='info']/span[@class='date']").all_text_contents()
블로그에 있는 글의 작성일자를 추출하는 css 구문 page.locator('div:nth-child(3) > div.titleWrap > div > span.date').text_content()
블로그에 있는 글의 개별 링크 URL들을 추출하는 xpath 구문 link_locators = page.locator("//div[@class='entry']/div[@class='titleWrap']/h2/a").all() for l_loc in link_locators: print(l_loc.get_attribute('href')) print(l_loc.text_content())
※ locator() 내부에서 명시적으로 css= xpath= 를 삽입할 수 있지만, 꼭 쓸 필요는 없다. page.locator("xpath=/html/body/div[1]/div/div[2]/div[2]/div/div[3]/div[1]/div/span[2]").text_content() """
from playwright.sync_api import Playwright, sync_playwright, expect import os from datetime import datetime
def run(playwright: Playwright) -> None:
# Get Current Working Directory current_dir = os.getcwd() if current_dir[-1] != '/': current_dir = current_dir + '/' #print(current_dir)
# Get Current Date and Time current_datetime = datetime.now().strftime("%Y-%m-%d %H-%M-%S") #print("Current Date & Time : ", current_datetime) # Convert datetime obj to string str_current_datetime = str(current_datetime)
## 브라우저가 화면에 나타나지 않도록 headless옵션을 켜고, 크롬브라우저를 사용합니다. ## headless=False 이면, 브라우저가 화면에 나타납니다. browser = playwright.chromium.launch(headless=True, channel="chromium") context = browser.new_context()
위의 경로명에서 /home/jetbrains/miniconda3 부분은 자신의 ssh 로그인 환경에 맞게 수정한다.
py_35978 이라는 가상환경 명도 자신의 환경에 맞게 수정한다.
※ 참고로 나는 추가적으로 python "$@" 줄을 python3 "$@"로 수정했다.
ssh 로그인 상태에서 chmod +x python_env 명령으로, 실행파일로 생성한다.
./python_env 명령을 실행하였을 때, 파이썬 쉘 환경이 되어야만 정상적인 것이다.
PyCharm에서 새로운 프로젝트를 생성하고, SSH 인터프리터를 원격 ssh 서버로 지정한 다음, Interpreter 를 기존의 /usr/bin/python 이 아니라, /home/jetbrains/python_env 와 같이, 앞에 생성한 쉘 스크립트의 정확한 경로명과 파일명을 적어준다.
새로운 프로젝트에서 Terminal 창을 열고서, 다음과 같은 파이썬 코드를 입력한다. import os print(os.environ.get('CONDA_PREFIX'))
아마 결과가 다음과 같이 출력되면, 정상적인 설정이 된 것이다. /home/jetbrains/miniconda3/envs/py_35978
1. items.py 파일에 아이템을 추가한다. 디폴트로 생성되어 있는 아이템 클래스를 그대로 이용할 수도 있고, 새로운 아이템 클래스를 생성해도 된다. 보통 디폴트 아이템 클래스는 아무것도 없다.
class AptCollectionItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field()
pass
그래서, 새로운 아이템 클래스를 생성한다. 반드시 필요한 항목은 files와 file_urls 이다. 일반파일을 다운로드하려면 FilesPipeline을 이용하는 것이고, 이미지 파일을 다운로드 하려면 ImagesPipeline을 이용하는 것이다. 현재는 일반파일을 다운로드 하는 것이므로, 아래는 FilesPipeline을 이용하는 경우를 설명한다.
class reportItem(scrapy.Item): title = scrapy.Field() # 제목 date = scrapy.Field() # 날짜 publisher = scrapy.Field() # 작성회사명 url = scrapy.Field() # 원본 URL files = scrapy.Field() # file_urls에 지정된 파일들에 대한 다운로드 결과가 저장되는 항목으로, 파일 1개에 대해서, url, path, checksum, status 가 dictionary 형태로 저장된다. ex) [{'url': 'http://.....', 'path': 'abcdefg.pdf', 'checksum': '2f88dd877d61e287fc1135a7ec0a2fa5', 'status': 'downloaded'}]
file_urls = scrapy.Field() # URL을 기준으로 scrapy가 다운로드 저장할 파일의 목록 ※ 반드시 List 자료형으로 넘겨줘야 함!!
files와 file_urls를 scrapy.Field()로 정의하면 된다.
※ files와 file_urls 이름을 바꾸고 싶다면, settings.py에서 아래와 같이 정의하면 된다.
ImagesPipeline을 이용하는 경우라면, images와 image_urls 명칭을 사용한다.
image_urls = scrapy.Field()
images = scrapy.Field()
images와 image_urls 명칭을 바꾸고 싶다면, settings.py에서 아래와 같이 정의하면 된다. IMAGES_URLS_FIELD = 'field_name_for_your_images_urls' IMAGES_RESULT_FIELD = 'field_name_for_your_processed_images'
기존에 있는 FilesPipeline 들은 모두 None 처리해주거나, 아예 지우는 것이 매우 중요하다. FILES_STORE 변수를 지정해서, 다운로드 되는 파일들이 저장되는 경로를 지정할 수 있다. 항상 하는 것이 좋다.
3. pipelines.py에 있는 파이프라인을 재정의한다. 디폴트로 생성되는 파이프라인은 다음과 같이 되어 있다.
class AptCollectionPipeline: def process_item(self, item, spider):
return item
이것을 다음과 같이, FilesPipeline 이나 ImagesPipeline 을 가지도록 다시 써야한다. Class 라인에서 Pipeline 클래스 이름 옆에 반드시 (FilesPipeline)이나 (ImagesPipeline) 구문이 추가되어야 한다. FilesPipeline과 ImagesPipeline 모듈 임포트도 해준다.
from scrapy.pipelines.files import FilesPipeline from scrapy.pipelines.files import ImagesPipeline
class AptCollectionPipeline(FilesPipeline): # 디폴트 처리를 주석처리하고, 파일명을 정해진 규칙대로 지정하기 위한 Override 함수 정의 def file_path(self, request, response=None, info=None, *, item=None): file_name: str = request.url.split("/")[-1] # 파일 수집 URL에 파일명이 있는 경우에는 URL 에서 파일명을 추출해서 사용
#file_name : str = request.meta['filename'] # 메타 정보를 이용하여, 이전 request 함수에서 파일명을 전달받은 수 있음
#file_name: str = item['filename'] # 추출되는 Item의 항목에 별도의 파일명을 지정할 수 있다면, 그 파일명을 가져와, 저장하는 파일 이름으로 사용 가능(파일의 저장 경로는 settings.py에 설정되어 있음!) # print("filename : ", file_name) return file_name
Pipeline을 재정의하면서, 반드시 필요한 파일명 지정에 대한 함수를 Override 하게 해야 한다.
파일명을 spider 함수쪽에서 item 항목으로 전달하거나, 또는 meta 항목에 넣어서 전달할 수도 있다. 아니면, request.url 쪽의 내용을 파싱해서 사용하는 방법도 있다. 또는, 다운로드 대상 파일 URL을 수집요청하는 호출 쪽에서 meta 값을 다음과 같이 정의해서 넘겨주면 된다.
meta = {'filename': item['name']} yield Request(url=file_url, meta=meta)
※ get_media_requests 함수도 Override 해야한다는 의견도 있는데, 확인해보니 각 Item에 수집되는 파일이 1개 인 경우에는 아무 상관이 없었다. 또한 한 Item에서 수집되는 파일이 여러개이더라도, 파일명을 위의 file_path 함수에서와 같이 url로부터 추출되는 경우라면 이상이 없었다. 그러나 한 Item에서 여러개의 파일이 수집되어야 하고, 파일명을 별도로 지정해야 하는 경우에는 get_media_requests 함수에서 파일명을 file_path로 넘겨줘야만 하므로, 다음과 같이 get_media_requests 함수를 재정의해서 추가해야 한다. ※ v2.3 버전에서는 item 이 파이프라인으로 정상적으로 넘어오지 않는 것 같은 현상이 있다. get_media_requests 함수를 반드시 정의해서 meta 값을 Request 인자로 사용해야만 했다. v2.4에서는 get_media_requests 함수가 없이 file_path 만 재 정의하여도 잘 동작하였다. def get_media_requests(self, item, info): for idx, file_url in enumerate(item['file_urls']): # 수집되어야할 파일들의 URL은 반드시 List 자료형으로 입력된다. #print("filename : ", item['file_names'][idx]) yield scrapy.Request(file_url, meta={'filename': item['file_names'][idx]}) # 각각 다운받을 파일 URL에 대한 요청을 하면서, meta 딕셔너리 값의 하나로 파일명 리스트에서 순번에 맞게 파일명을 넘겨준다. 이것때문에 이 함수를 재정의하였다.
※ 파일이 다운로드 되고나서, 처리를 해주는 함수도 있다. (아마 파일이 정상적이지 않은지 확인하는 방법으로 유용할 듯 하다.) from itemadapter import ItemAdapter from scrapy.exceptions import DropItem
def item_completed(self, results, item, info): file_paths = [x['path'] for ok, x in results if ok] if not file_paths: raise DropItem("Item contains no files") adapter = ItemAdapter(item) adapter['file_paths'] = file_paths return item
4. spider 함수에서 item을 생성하면, Scrapy에 의해 파일이 자동으로 수집된다.
item['file_urls'] = [url_1, url_2, url_3] # URL을 기준으로 scrapy가 다운로드 저장할 파일의 목록 ※ 반드시 List 자료형으로 넘겨줘야 함!! yield item
5. 만약 crawl 하는 과정에서 파일 다운로드가 되지 않고, 302 에러가 발생한다면, 파일 다운로드 URL이 redirect되는 경우이므로,
# cd ~/.vim/bundle # git clone https://github.com/fatih/vim-go.git
홈 디렉토리에 .vimrc 파일을 생성하고, 다음과 같이 작성한다.
# vi ~/.vimrc execute pathogen#infect() syntax on filetype plugin indent on
vim을 실행하고 :GoInstallBinaries명령을 실행하면, vim-go 관련된 플러그인들이 자동으로 설치된다.
4. YouCompleteMe(이하 YCM)은 VIM을 위한 자동코드완성 엔진이다. YCM은 C, C++, Object-C, Object-C++, CUDA, Python2, Pyton3, C#, Go 등 다양한 언어에 대한 자동완성기능을 제공한다. YCM을 클론하고 컴파일 한다. Go 자동완성을 지원하고 싶다면 --go-completer 를 컴파일 옵션으로 설정해야 한다.
# cd ~/.vim/bundle # git clone https://github.com/Valloric/YouCompleteMe.git # cd ~/.vim/bundle/YouCompleteMe # git submodule update --init --recursive # ./install.sh --go-completer
5.Tagbar Tagbar 플러그인을 이용해서 현재 파일의 태그를 탐색해서, 코드의 대략적인 구조를 빠르게 살펴볼수 있다.