2023년 1월 29일 일요일

python selenium 으로 post 사용하기

selenium post 

 selenium 에서 post 를 사용하기 위해서는 selenium-requests 라는 패키지를 설치해서 사용하는 방식이 있습니다. selenium-requests 를 사용하게 되면 selenium 으로 주로 사용했던 키동작이라던가 그런 부분을 사용을 할 수 없습니다.

그리고 selenium 과  selenium-requests 는 각각의 브라우저로 동작하기 때문에 필요한 부분에서만 처리를 하고자 하는 경우 web session 유지가 되지 않습니다. 예를 들어 selenium 를 이용하여 로그인 처리를 한 후 selenium-requests 를 이용하고자 한다면 로그인이 유지가 안된다는 뜻입니다.

그래서 selenium 으로만 post 할 수 있는 방법에 대해 알아 보았습니다.

아무곳이나 테스트할 수 없기 때문에 예전에 사용하던 flask 를 이용해서 간단하게 post 테스트 할 수 있는 환경을 만들었습니다.

from flask import Flask, make_response, jsonify, request

stock = {
	"fruit": {
		"apple": 10,
		"banana": 20
	}
}

app = Flask(__name__)

@app.route("/stock")
def get_stock():
	res = make_response(jsonify(stock), 200)
	return res
 
@app.route("/stock/<goods>")
def get_goods(goods):
	""" Returns a goods from stock """
	if goods in stock:
		res = make_response(jsonify(stock[goods]), 200)
		return res
	res = res = make_response(jsonify({"error": "Not found"}), 404)
	return res

@app.route("/stock/<goods>", methods=["POST"])
def create_goods(goods):
	""" Creates a new goods if it doesn't exist """
	req = request.get_data()
	print("create_goods")
	print(req)
	res = make_response(jsonify({"message": "goods created"}), 201)
	return res

if __name__ == '__main__':
	app.run(debug=True)

크게 내용은 없고 post가 들어오면 어떤 식으로 들어온 것인지 그대로 출력만 해줍니다.

그리고 다음으로는 selenium 을 래핑한 함수입니다. 이전 예제에서도 주로 사용했어서 

이름은 selenium_v1.py 로 지었습니다.

import chromedriver_autoinstaller
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
import os

class selenium_v:
	def __init__(self):
		self.driver = None
		self.download_path = None
		return
		
	def create_web_driver_chrome(self, headless=True, download_path=None):
		options = webdriver.ChromeOptions()
		options.add_argument('disable-gpu')
		options.add_experimental_option('excludeSwitches',['enable-logging'])
		
		if headless:
			options.add_argument('headless')
		
		if download_path!=None:
			self.download_path = os.path.abspath(download_path)
			prefs = {"download.default_directory":self.download_path}
			options.add_experimental_option("prefs",prefs)
		
		if self.driver!=None:
			self.driver.close()
		self.driver = None
		
		chromedriver_autoinstaller.install()
		
		try:
			self.driver = webdriver.Chrome(options=options)
			self.driver.implicitly_wait(10)
		except Exception as e:
			print("exception",e)
		
		return self.driver
		
	def download_wait(self, timeout_min=1):
		if self.download_path==None:
			print("error can not find download path")
			return -2
		path_to_downloads = self.download_path
		seconds = 0
		dl_wait = True
		sum_after = 0
		while dl_wait and seconds < timeout_min*60:
			time.sleep(5)
			dl_wait = False
			sum_before = sum_after
			sum_after = 0
			for fname in os.listdir(path_to_downloads):
				if fname.endswith('.crdownload'):
					sum_after += os.stat(path_to_downloads+'/'+fname).st_size
					dl_wait = True
			if dl_wait and seconds > 10 and sum_before == sum_after:
				print("download timeout")
				dl_wait = False
				return -1
			seconds += 5
		return seconds
	
	def get(self,url):
		if self.driver == None:
			return -1
		return self.driver.get(url)
		
	def close(self):
		if self.driver == None:
			return -1
		return self.driver.close()
		
	def save_page_source(self, filename):
		if self.driver == None:
			return -1
		html = self.driver.page_source
		try:
			f = open(filename, 'w', encoding = 'utf-8')
			f.write(html)
			f.close()
		except:
			print("exception",e)
		return 0
		
if __name__ == "__main__":
	sel = selenium_v()
	sel.create_web_driver_chrome(headless=True,download_path=".")
	print(sel.get("https://www.daum.net"))
	print(sel.driver.page_source)
	print(sel.get("https://www.python.org/ftp/python/3.9.11/python-3.9.11-embed-amd64.zip"))
	sel.download_wait()
	print(sel.driver.page_source)
	sel.save_page_source("test.html")

다음의 코드가 핵심 코드입니다.

import requests
import selenium_v1 as selenium_v
from bs4 import BeautifulSoup

def print_roundtrip(response, *args, **kwargs):
	format_headers = lambda d: '\n'.join(f'{k}: {v}' for k, v in d.items())
	print((""
		"---------------- request ----------------\n"
		"{req.method} {req.url}\n"
		"{reqhdrs}\n"
		"\n"
		"{req.body}\n"
		"---------------- response ----------------\n"
		"{res.status_code} {res.reason} {res.url}\n"
		"{reshdrs}\n"
		"\n"
		"{res.text}\n"
		"").format(
		req=response.request, 
		res=response, 
		reqhdrs=format_headers(response.request.headers), 
		reshdrs=format_headers(response.headers), 
	))
data1={"cat":1,"dog":2}
data2={"cat":3,"lion":4,"bird":2}
data3={"cat":3,"dog":4}

resp = requests.post('http://127.0.0.1:5000/stock/animal', data=data2, hooks={'response': print_roundtrip})


def get_script(url, params):
	post_script = '''
	function post_to_url(path, params, method) {
		method = method || "post";
		var form = document.createElement("form");
		form._submit_function_ = form.submit;
		form.setAttribute("method", method);
		form.setAttribute("action", path);
		for(var key in params) {
			var hiddenField = document.createElement("input");
			hiddenField.setAttribute("type", "hidden");
			hiddenField.setAttribute("name", key);
			hiddenField.setAttribute("value", params[key]);
			form.appendChild(hiddenField);
		}
		document.body.appendChild(form);
		form._submit_function_();
	}

	'''
	run_script = f'post_to_url("{url}",'
	run_script = run_script + "{"
	first = True
	for odata in params:
		if first == False:
			run_script = run_script + ","
		else:
			first = False
		run_script = run_script + odata + ':' + str(params[odata])
	run_script = run_script + "} )"
	#print(post_script + run_script)
	return post_script + run_script

sel = selenium_v.selenium_v()
sel.create_web_driver_chrome(headless=True, download_path=".")
sel.driver.execute_script(get_script("http://127.0.0.1:5000/stock/animal", data1))
print(sel.driver.page_source)

# get page
#sel.get('http://127.0.0.1:5000/stock/animal')
#sel.driver.implicitly_wait(3)

requests 와 selenium 을 비교하기 위해서 requests 예제가 위쪽에 있습니다.

핵심은 selenium 의 execute_script 를 이용하게 됩니다.

스크립트를 실행시키기 위해서 스크립트를 구성하는 함수가 get_script 함수입니다.

get_script( 접속하고자 하는url 정보, 전달하고자 하는 data의 dict 타입 필요합니다. )

script의 내용은 javascript 형태로 form을 만들고 post를 하게 되는 내용입니다. default가 post가되고 get으로 구현할 수 도 있습니다.


실행 결과

서버쪽 실행 켤과를 살펴보면 아래와 같이 나오게 되는데 

앞쪽 결과가 requests로 넘긴 결과 입니다.

create_goods
b'cat=3&lion=4&bird=2'
127.0.0.1 - - [29/Jan/2023 20:31:41] "POST /stock/animal HTTP/1.1" 201 -
create_goods
b'cat=1&dog=2'
127.0.0.1 - - [29/Jan/2023 20:31:43] "POST /stock/animal HTTP/1.1" 201 -

차이가 없으며 출력 값이 다른 것은 보낼때 data1,data2 각각 다른 값을 보냈기에 다른것입니다.


보내는쪽 결과

아래는 requests 로 보낸 결과 입니다.

---------------- request ----------------
POST http://127.0.0.1:5000/stock/animal
User-Agent: python-requests/2.28.1
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: keep-alive
Content-Length: 19
Content-Type: application/x-www-form-urlencoded

cat=3&lion=4&bird=2
---------------- response ----------------
201 CREATED http://127.0.0.1:5000/stock/animal
Server: Werkzeug/2.2.2 Python/3.8.8rc1
Date: Sun, 29 Jan 2023 11:31:41 GMT
Content-Type: application/json
Content-Length: 33
Connection: close

{
  "message": "goods created"
}


아래는 다음 코드에 의해서 나오는 값입니다.

print(sel.driver.page_source)


<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">{
  "message": "goods created"
}
</pre></body></html>


댓글 없음:

댓글 쓰기