Package: hxsoup
이 설명은 최신 버전이 아닐 수 있습니다. 만약 최신 버전을 확인하고 싶으시다면이 깃허브 링크를 참고하세요.hxsoupVarious convenient features related to httpx and BeautifulSoup.(httpx+ BeautifulSoup)hxsoup는 httpx를 기반으로 추가적인 기능을 추가한 라이브러리입니다.Getting started파이썬을 설치하고 터미널에 다음과 같은 명령어를 입력하세요.pip install -U resouprequests와 bs4는 같이 설치되지만 BeatifulSoup의 추가적인 parser인 lxml와 html5lib는 기본으로 제공하지 않습니다.How to use[!NOTE]
예시에서는 get 요청을 위주로 설명하지만, 다른 모든 메소드(options/head/post/put/patch/delete)에서도 동일하게 작동합니다.attempts어떤 경우에서든 서버와의 연결이 실패할 수 있습니다. 이유는 다양할 수 있으나, 그저 다시 시도하는 것만으로도 해결되는 경우가 태반입니다.만약 attempts를 1보다 큰 정수로 설정하면 연결을 실패했을 때 해당 숫자만큼 재시도합니다. 그리고 만약 결국 attempts 만큼 도전했는데도 불구하고 연결에 실패했을 경우 오류를 re-raise합니다.연결에 바로 성공했을 경우:>>>importhxsoup.devashd>>>hd.get("https://python.org",attempts=3)<Response[200OK]>[!NOTE]hxsoup.dev는 hxsoup와 거의 같지만 일부 기본값을 조정한 모듈입니다. hxsoup와 같다고 생각하셔도 무관합니다.
자세한 내용은 뒤에서 설명합니다.연결에 끝까지 실패했을 경우:>>>importhxsoup.devashd>>>hd.get("https://unreachable-service.com",attempts=3)WARNING:root:Retrying...WARNING:root:Retrying...Traceback(mostrecentcalllast):...httpx.ConnectError:[Errno11001]getaddrinfofailed첫 연결에 실패하고 다시 몇 번 시도했을 때 성공했을 경우:>>>importhxsoup.devashd>>>hd.get('https://www.webtoons.com/en/',attempts=4)WARNING:root:Retrying...WARNING:root:Retrying...WARNING:root:Successfullyretrieved:'https://www.webtoons.com/en/'<Response[200OK]>raise_for_statushttpx에는 raise_for_status라는 기능이 있습니다.response.raise_for_status()를 이용하면 상태 코드가 일반적이지 않을 때 오류를 냅니다.>>>importhxsoup.devashd>>>response=hd.get("https://httpbin.org/status/404")>>>response.raise_for_status()Traceback(mostrecentcalllast):...httpx.HTTPStatusError:Clienterror'404 NOT FOUND'forurl'https://httpbin.org/status/404'Formoreinformationcheck:https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404hxsoup에서는raise_for_status를 파라미터에서 그대로 사용할 수 있도록 합니다.>>>importhxsoup.devashd>>>response=hd.get("https://httpbin.org/status/404",raise_for_status=True)Traceback(mostrecentcalllast):...httpx.HTTPStatusError:Clienterror'404 NOT FOUND'forurl'https://httpbin.org/status/404'Formoreinformationcheck:https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404Client에raise_for_status를 추가하면 해당 클라이언트에서는 모두 raise_for_status가 적용됩니다.>>>importhxsoup.devashd>>>withhd.Client(raise_for_status=True)asclient:...try:...client.get("https://httpbin.org/status/404")...exceptExceptionase:...print(e)...try:...client.get("https://httpbin.org/status/404")...exceptExceptionase:...print(e)...Clienterror'404 NOT FOUND'forurl'https://httpbin.org/status/404'Formoreinformationcheck:https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404Clienterror'404 NOT FOUND'forurl'https://httpbin.org/status/404'Formoreinformationcheck:https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404일반적으로 상태 코드 이상은 모든 연결이 안정적으로 동작한 경우가 많기 때문에 raise_for_status에 의한 오류는 attempts에 의해 필터링되지 않습니다.Clienthxsoup.Client는 httpx.Client에 대응하는 기능입니다. 위에서 설명되었던 모든 추가 파라미터들(attempts, raise_for_status)는 Client에서도 사용할 수 있으며 Client를 initialize할 때 사용한다면 해당 client의 모든 통신에서 적용시킬 수 있습니다.>>>withhxsoup.Client(raise_for_status=True)asclient:...# Client를 initialize할 때 raise_for_status를 True로 했기 때문에...# raise_for_status를 직접 적지 않았다면 raise_for_status가 자동을 적용됨....client.get("https://httpbin.org/status/404")...Traceback(mostrecentcalllast):...httpx.HTTPStatusError:Clienterror'404 NOT FOUND'forurl'https://httpbin.org/status/404'Formoreinformationcheck:https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404하지만 개별 request에 해당 파라미터와 다른 파라미터를 적용했다면 해당 파라미터가 적용됩니다.SoupTools, SoupedResponseSoupTools는BeautifulSoup에 몇 가지 편리한 기능을 추가하고,SoupedResponse는BeautifulSoup를 respone 안에 멋지게 통합합니다.SoupedResponse는httpx.Response의 subclass이면서SoupTools의 subclass이며,
hxsoup.dev를 포함하여 hxsoup의 모든 response는 SoupedResponse입니다.TheSoupToolsmethods that correspond to methods in BeautifulSoupSoupTools에는 BeautifulSoup의 메소드에 대응하는 메소드들이 있습니다.SoupTools.soup():BeautifulSoup(...)에 대응합니다.SoupTools.soup_select(selector):BeautifulSoup(...).select(selector)에 대응합니다.SoupTools.soup_select_one(selector):BeautifulSoup(...).select_one(selector)에 대응합니다.no_empty_resultBeautifulSoup.soup_select_one()에는 한 가지 문제가 있습니다. 바로 해당하는 element를 찾는데 실패했을 때 오류가 아닌 None을 내밷는다는 점입니다. 이는 어쩔 때는 편리할지는 몰라도 typing도 어렵고.soup_select().text와 같은 chaining도 어렵게 합니다.hxsoup의response.soup_select_one()에는 이러한 문제를 해결하기 위해no_empty_result라는 파라미터를 도입했습니다.
아래는python.org에never-gonna-selected라는 선택자에 맞는 태그가 있는지 확인하고 있습니다.python.org에서는 그런 태그는 없기 때문에 일반적으로는 None을 반환받게 되는데,no_empty_result가 True라면 None이 아닌 오류를 raise하게 됩니다.>>>importhxsoup.devashd>>>res=hd.get("https://python.org")>>>res.soup_select_one("never-gonna-selected",no_empty_result=True)Traceback(mostrecentcalllast):...hxsoup.exceptions.EmptyResultError:SelectingresultisNone.ThiserrorhappensprobablybecauseofinvalidselectororURL.CheckwhetherselectorandURLarebothvalid.statuscode:HTTP200OK,URL:https://www.python.org/,selector:'never-gonna-selected'같은 방식이 response.soup_select()에도 적용됩니다. 이 경우 빈 리스트가 리턴될 때 EmptyResultError가 나게 됩니다.>>>importhxsoup.devashd>>>res=hd.get("https://python.org")>>>res.soup_select("never-gonna-selected",no_empty_result=True)Traceback(mostrecentcalllast):...hxsoup.exceptions.EmptyResultError:Selectingresultisemptylist("[]").ThiserrorhappensprobablybecauseofinvalidselectororURL.CheckwhetherselectorandURLarebothvalid.statuscode:HTTP200OK,URL:https://www.python.org/,selector:'never-gonna-selected'response.soup_select()에도no_empty_result가 있다는 점을 잊지 마세요. 따라서Client나hxsoup.get자체에no_empty_result를 사용할 때에는 이 기능이response.soup_select()에도 적용된다는 점을 명심하세요.parserparser을 설정할 수 있도록 합니다. BeautifulSoup의 용어로는feature입니다.기본적으로는'html.parser'를 사용하도록 되어 있습니다.Broadcastingsoup_select()의 결과는 리스트입니다. Tag 관련 처리를 할 때에는 리스트 컴프리헨션을 이용해야 하는데, 여간 귀찮은 일이 아닙니다.hxsoup에서soup_select()의 결과는 BroadcastList이며, 이는 여러 문제를 해결합니다.BroadcastList는.bc를 붙이면 브로드캐스팅 가능한 상황이 되고 그 뒤에 어떤 것을 붙이던 브로드캐스팅이 일어납니다.예를 들면 다음의 코드는>>>importhxsoup.devashd>>>res=hd.get("https://python.org")>>>[tag.textfortaginres.soup_select("strong")]['Notice:','A A','relaunched community-run job board']아래의 코드로 대체될 수 있습니다.>>>importhxsoup.devashd>>>res=hd.get("https://python.org")>>>res.soup_select("strong").bc.text['Notice:','A A','relaunched community-run job board']브로드캐스팅을 더 하고 싶다면.bc를 또 붙이면 됩니다.>>>importhxsoup.devashd>>>res=hd.get("https://python.org")>>>res.soup_select("strong").bc.text.bc[::2]['Ntc:','AA','rluce omnt-u o or']ClientOptions고정적으로 여러 request에 대해 같은 키워드를 사용해야 하는 경우가 있습니다. 대부분은 Client를 이용하면 해결되지만, AsyncClient와 Client를 같이 사용하거나, httpx.get처럼 클라이언트 없이 사용하고 싶은 경우도 있을 것입니다. 이럴 경우 이 클래스를 사용할 수 있습니다.(.venv)C:\Users\USER\Programming\vscode\git\hxsoup>py-3.12-masyncioasyncioREPL3.12.1(tags/v3.12.1:2305ca5,Dec72023,22:03:25)[MSCv.193764bit(AMD64)]onwin32Use"await"directlyinsteadof"asyncio.run()".Type"help","copyright","credits"or"license"formoreinformation.>>>importasyncio>>>fromhxsoupimportClientOptions>>>>>>options=ClientOptions(follow_redirects=True)>>>print(options.get("https://python.org"))<Response[200OK]>>>>withoptions.build_client()asclient:...print(client.get("https://python.org"))...<Response[200OK]>>>>asyncwithoptions.build_async_client()asasync_client:...print(awaitclient.get("https://python.org"))...<Response[200OK]>hxsoup.dev일부 기본값을 재조정하고 caching이 포함된 모듈입니다.Adjusted defaults개발 중에는 일부 파라미터가 흔히 많이 사용됩니다. 예를 들어follow_redirects파라미터는 httpx에서 기본적으로 꺼져 있지만 이른 개발 사이클에 있는 경우 켜져 있는 것이 편한 경우가 많습니다.개발자의 편의를 위해follow_redirects를 비롯한 몇몇 파라미터들은 httpx의 기본 설정값과는 다른 값을 이용합니다.파라미터 이름hxsoup 기본값hxsoup.dev기본값follow_redirectsFalseTrueheadersNoneDEV_HEADERS>>>importhxsoup>>>hxsoup.get("https://python.org")<Response[301MovedPermanently]>>>>>>>importhxsoup.devashd>>># follow_redirects가 True임.>>>hd.get("https://python.org")<Response[200OK]>DEV_HEADERS의 내용은 Chrome 브라우저의 기본 헤더이며 내용은 다음과 같습니다.{"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7","Accept-Encoding":"gzip, deflate, br","Accept-Language":"ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7","Sec-Ch-Ua":'"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',"Sec-Ch-Ua-Arch":'"x86"',"Sec-Ch-Ua-Bitness":'"64"',"Sec-Ch-Ua-Full-Version-List":'"Not_A Brand";v="8.0.0.0", "Chromium";v="120.0.6099.130", "Google Chrome";v="120.0.6099.130"',"Sec-Ch-Ua-Mobile":"?0","Sec-Ch-Ua-Model":'""',"Sec-Ch-Ua-Platform":'"Windows"',"Sec-Ch-Ua-Platform-Version":'"15.0.0"',"Sec-Ch-Ua-Wow64":"?0","Sec-Fetch-Dest":"document","Sec-Fetch-Mode":"navigate","Sec-Fetch-Site":"none","Sec-Fetch-User":"?1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",}cached requests활발한 개발 중에는, 특히 Jupyter를 이용할 때는, 일부 requests를 계속해서 보내게 되는 경우가 있습니다. 이는 서버에 부담을 주고 개발 속도를 늦춥니다.각 메소드의 앞에 c를 붙이면 응답이 캐싱됩니다.예를 들어 시간을 비교하면 아래와 같이 됩니다.>>>fromtimeitimporttimeit>>>timeit('hd.get("https://python.org")',setup="import hxsoup.dev as hd",number=10)0.7851526000013109>>>timeit('hd.cget("https://python.org")',setup="import hxsoup.dev as hd",number=10)0.061434000002918765options/head/post/put/patch/delete들도 마찬가지로 대응되는 coptions/chead/cpost/cput/cpatch/cdelete가 있습니다.>>>fromtimeitimporttimeit>>>timeit('hd.post("https://httpbin.org/post")',setup="import hxsoup.dev as hd",number=10)9.307660000005853>>>timeit('hd.cpost("https://httpbin.org/post")',setup="import hxsoup.dev as hd",number=10)0.8557240999944042캐시는 lru_cache 기본값을 사용하기 때문에 메소드 구분 없이 128개까지 저장됩니다.Client/ClientOptionsClient와 ClientOptions에도 마찬가지로 기본 옵션을 사용 가능합니다.>>>importhxsoup.devashd>>>importhxsoup>>>>>>client=hxsoup.Client()>>>client.get("https://python.org")<Response[301MovedPermanently]>>>>>>>dev_client=hd.Client()>>>dev_client.get("https://python.org")<Response[200OK]>>>>>>>options=hxsoup.ClientOptions()>>>options.get("https://python.org")<Response[301MovedPermanently]>>>>>>>dev_options=hd.ClientOptions()>>>dev_options.get("https://python.org")<Response[200OK]>License information이 프로그램의 일부는resoup(본인 제작)라이브러리에 있던 코드를 포함합니다.
Some part of this program contains code fromresoup(created and developed by me)library.이 프로그램의 일부는httpx(BSD-3-Clause license)라이브러리에 있던 코드를 포함합니다.
Some part of this program contains code fromtypeshedlibrary.Motivation and blathers이전에 requests의 불편한 점을 느끼고 관련 내용을 입맛에 맞게 수정한 resoup라는 라이브러리를 만들어 사용하고 있었습니다.그러던 어느 날ArjanCodes의 영상에서 httpx에 대해 다시 보게 되고 실제로 사용해보니 requests와 상위 호환되는 라이브러리라는 점을 알게 되었습니다.따라서 httpx을 앞으로의 프로젝트들에서 사용하기로 결정했고, resoup의 기능을 포기할 수 없었기에 requests의 경우와 마찬가지로 resoup에 대응하는 httpx에 대한 유틸리티를 만들기로 했으며 그 결과가 hxsoup입니다.resoup과 비교했을 때 개발 경험은 hxsoup 쪽이 압도적으로 좋았는데, requests는 type hint가 나오기 전 라이브러리라 그런지 효율적이지만 type hint를 적용하기에는 최악이었던 반면, httpx는 따로 type stub나 typing.overload를 거의 사용하지 않았을 정도로 매우 안정적이고 typing을 적용하면서 개발하기에도 좋았습니다. (물론 resoup을 만들면서 생긴 노하우도 많이 도움이 되었겠지만요.)Changelog0.4.1 (2023-01-24): caching 제거, broadcasting 마저 제거.0.4.0 (2023-01-23): NotEmptySoupTools와 NotEmptySoupedResponse 추가, soup_select와 soup_select_one에 **kwargs 추가, 버그 수정0.3.0 (2023-01-04): ClientOptions 추가, 코드 및 버그 수정0.2.0 (2023-01-01): 여러 기능을 추가하고 수정.0.1.0 (2023-12-28): 첫 (프리)릴리즈