Skip to content

Commit

Permalink
Merge branch 'main' into feat-black-formatter
Browse files Browse the repository at this point in the history
  • Loading branch information
Re-st authored Oct 13, 2023
2 parents 5428e2e + 970120f commit c1562e9
Show file tree
Hide file tree
Showing 12 changed files with 990 additions and 211 deletions.
45 changes: 23 additions & 22 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,39 @@
---
name: Bug report
about: Create a report to help us improve
about: 버그 세부 사항을 입력해 주세요.
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.
## 버그 설명
버그가 무엇인지 명확하고 간결하게 설명합니다.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
## 재현 방법
버그를 재현하는 단계를 설명해 주세요:
1. '...'로 이동
2. '....'를 클릭
3. '....'까지 아래로 스크롤
4. 오류 발생

**Expected behavior**
A clear and concise description of what you expected to happen.
## 예상된 결과
정상적인 경우 예상되는 상황에 대해 명확하고 간결하게 설명합니다.

**Screenshots**
If applicable, add screenshots to help explain your problem.
## 스크린샷
해당되는 경우 문제를 설명하는 데 도움이 되는 스크린샷을 추가하세요.

**Desktop (please complete the following information):**
## 문제 발생 환경
**데스크탑 (다음 정보를 입력해 주세요):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
- 브라우저 [e.g. chrome, safari]
- 버전 [e.g. 22]

**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
**모바일 (다음 정보를 입력해 주세요):**
- 기기명: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
- 브라우저 [e.g. chrome, safari]
- 버전 [e.g. 22]

**Additional context**
Add any other context about the problem here.
## 추가 정보
문제에 대한 추가적인 정보가 있다면 입력해 주세요.
18 changes: 9 additions & 9 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
about: Feature 작업 사항을 입력해 주세요.
title: ''
labels: ''
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
## 문제 상황
해결하려는 문제가 있다면 무엇인지 명확하고 간결하게 설명합니다. (ex. [...] 가 안 돼서 짜증나요!)

**Describe the solution you'd like**
A clear and concise description of what you want to happen.
## 해결 방안 제안
해결하려는 방법을 명확하고 간결하게 설명합니다.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
## 고려한 다른 대안들
고려해 본 다른 대안이나 기능에 대해 명확하고 간결하게 설명합니다.

**Additional context**
Add any other context or screenshots about the feature request here.
## 추가 정보
여기에 기능 요청에 대한 다른 상황이나 스크린샷을 추가하세요.
197 changes: 116 additions & 81 deletions scrap/local_councils/basic.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,39 @@
from urllib.parse import urlparse

from scrap.utils.types import CouncilType, Councilor, ScrapResult
from scrap.utils.types import CouncilType, Councilor, ScrapResult, ScrapBasicArgument
from scrap.utils.requests import get_soup
from scrap.utils.utils import getPartyList
import re
import requests
import copy

regex_pattern = re.compile(r"정\s*\S*\s*당", re.IGNORECASE) # Case-insensitive
party_keywords = getPartyList()
party_keywords.append("무소속")

pf_elt = [None, "div", "div"]
pf_cls = [None, "profile", "profile"]
pf_memlistelt = [None, None, None]
def find(soup, element, class_):
if class_ is None:
return soup.find(element)
else:
return soup.find(element, class_)

name_elt = [None, "em", "em"]
name_cls = [None, "name", "name"]
name_wrapelt = [None, None, None]
name_wrapcls = [None, None, None]

pty_elt = [None, "em", "em"]
pty_cls = [None, None, None]
pty_wrapelt = [None, None, None]
pty_wrapcls = [None, None, None]
def find_all(soup, element, class_):
if class_ is None:
return soup.find_all(element)
else:
return soup.find_all(element, class_)


def get_profiles(soup, element, class_, memberlistelement):
def get_profiles(soup, element, class_, memberlistelement, memberlistclass_):
# 의원 목록 사이트에서 의원 프로필을 가져옴
if memberlistelement is not None:
soup = soup.find_all(memberlistelement, class_="memberList")[0]
return soup.find_all(element, class_)

try:
soup = find_all(soup, memberlistelement,
class_=memberlistclass_)[0]
except Exception:
raise RuntimeError('[basic.py] 의원 목록 사이트에서 의원 프로필을 가져오는데 실패했습니다.')
return find_all(soup, element, class_)

def getDataFromAPI(url_format, data_uid, name_id, party_id) -> Councilor:
# API로부터 의원 정보를 가져옴
Expand All @@ -42,27 +45,36 @@ def getDataFromAPI(url_format, data_uid, name_id, party_id) -> Councilor:
)



def get_name(profile, element, class_, wrapper_element, wrapper_class_):
# 의원 프로필에서 의원 이름을 가져옴
if wrapper_element is not None:
profile = profile.find_all(wrapper_element, class_=wrapper_class_)[0]
name_tag = profile.find(element, class_)
profile = find_all(profile, wrapper_element, class_=wrapper_class_)[0]
name_tag = find(profile, element, class_)
if name_tag.find('span'):
name_tag = copy.copy(name_tag)
# span 태그 안의 것들을 다 지움
for span in name_tag.find_all('span'):
span.decompose()
name = name_tag.get_text(strip=True) if name_tag else "이름 정보 없음"
if len(name) > 10: # strong태그 등 많은 걸 name 태그 안에 포함하는 경우. 은평구 등.
name = name_tag.strong.get_text(strip=True) if name_tag.strong else "이름 정보 없음"
name = name.split("(")[0].split(":")[-1] # 이름 뒷 한자이름, 앞 '이 름:' 제거

# 수식어가 이름 뒤에 붙어있는 경우
while len(name) > 5:
if name[-3:] in ["부의장"]: # 119 등.
name = name[:-3].strip()
else:
break
while len(name) > 4:
if name[-2:] in ["의원", "의장"]: # 강서구 등.
name = name[:-2].strip()
else:
break # 4자 이름 고려.

# name은 길고 그 중 strong태그 안에 이름이 있는 경우. 은평구, 수원시 등.
if name_tag.strong is not None:
name = name_tag.strong.get_text(
strip=True) if name_tag.strong else "이름 정보 없음"
name = name.split('(')[0].split(
':')[-1].strip() # 이름 뒷 한자이름, 앞 '이 름:' 제거
# TODO : 만약 이름이 우연히 아래 단어를 포함하는 경우를 생각해볼만 함.
if len(name) > 3:
# 수식어가 이름 앞이나 뒤에 붙어있는 경우
for keyword in ['부의장', '의원', '의장']: # 119, 강서구 등
if keyword in name:
name = name.replace(keyword, '').strip()
for keyword in party_keywords:
if keyword in name: # 인천 서구 등
name = name.replace(keyword, '').strip()
break
name = name.split(' ')[0] # 이름 뒤에 직책이 따라오는 경우
return name


Expand All @@ -73,68 +85,90 @@ def extract_party(string):
return None


def get_party(
profile, element, class_, wrapper_element, wrapper_class_, party_in_main_page, url
):
# 의원 프로필에서 의원이 몸담는 정당 이름을 가져옴
if not party_in_main_page:
parsed_url = urlparse(url)
base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
# 프로필보기 링크 가져오기
profile_link = profile.find("a", class_="start")
profile_url = base_url + profile_link["href"]
def goto_profilesite(profile, wrapper_element, wrapper_class_, wrapper_txt, url):
# 의원 프로필에서 프로필보기 링크를 가져옴
parsed_url = urlparse(url)
base_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
# 프로필보기 링크 가져오기
profile_link = find(profile, wrapper_element, class_=wrapper_class_)
if wrapper_txt is not None:
profile_links = find_all(profile, 'a', class_=wrapper_class_)
profile_link = [
link for link in profile_links if link.text == wrapper_txt][0]
if profile_link is None:
raise RuntimeError('[basic.py] 의원 프로필에서 프로필보기 링크를 가져오는데 실패했습니다.')
# if base_url[-1] != '/':
# base_url = base_url + '/'
profile_url = base_url + profile_link['href']
try:
profile = get_soup(profile_url, verify=False)
party_pulp_list = list(
filter(
lambda x: regex_pattern.search(str(x)), profile.find_all(element, class_)
)
)
except Exception:
raise RuntimeError('[basic.py] \'//\'가 있진 않나요?', ' url: ', profile_url)
return profile


def get_party(profile, element, class_, wrapper_element, wrapper_class_, wrapper_txt, url):
# 의원 프로필에서 의원이 몸담는 정당 이름을 가져옴
if wrapper_element is not None:
profile = goto_profilesite(
profile, wrapper_element, wrapper_class_, wrapper_txt, url)
party_pulp_list = list(filter(lambda x: regex_pattern.search(
str(x)), find_all(profile, element, class_)))
if party_pulp_list == []:
raise RuntimeError('[basic.py] 정당정보 regex 실패')
party_pulp = party_pulp_list[0]
party_string = party_pulp.get_text(strip=True)
party_string = party_string.split(" ")[-1].strip()
party_string = party_pulp.get_text(strip=True).split(' ')[-1]
while True:
if (party := extract_party(party_string)) is not None:
return party
if (party_span := party_pulp.find_next("span")) is not None:
party_string = party_span.text.split(" ")[-1]
if (party_pulp := party_pulp.find_next('span')) is not None:
party_string = party_pulp.text.strip().split(' ')[-1]
else:
return "정당 정보 파싱 불가"
return "[basic.py] 정당 정보 파싱 불가"


def get_party_easy(profile, wrapper_element, wrapper_class_, wrapper_txt, url):
# 의원 프로필에서 의원이 몸담는 정당 이름을 가져옴
if wrapper_element is not None:
profile = goto_profilesite(
profile, wrapper_element, wrapper_class_, wrapper_txt, url)
party = extract_party(profile.text)
assert (party is not None)
return party

def scrap_basic(url, cid, encoding="utf-8") -> ScrapResult:
"""의원 상세약력 스크랩

def scrap_basic(url, cid, args: ScrapBasicArgument, encoding='utf-8') -> ScrapResult:
'''의원 상세약력 스크랩
:param url: 의원 목록 사이트 url
:param n: 의회 id
:param encoding: 받아온 soup 인코딩
:return: 의원들의 이름과 정당 데이터를 담은 ScrapResult 객체
"""
'''
soup = get_soup(url, verify=False, encoding=encoding)
councilors: list[Councilor] = []
party_in_main_page = any(keyword in soup.text for keyword in party_keywords)

profiles = get_profiles(
soup, pf_elt[cid - 1], pf_cls[cid - 1], pf_memlistelt[cid - 1]
)
print(cid, "번째 의회에는,", len(profiles), "명의 의원이 있습니다.") # 디버깅용.
profiles = get_profiles(soup, args.pf_elt, args.pf_cls,
args.pf_memlistelt, args.pf_memlistcls)
print(cid, '번째 의회에는,', len(profiles), '명의 의원이 있습니다.') # 디버깅용.

for profile in profiles:
name = get_name(
profile,
name_elt[cid - 1],
name_cls[cid - 1],
name_wrapelt[cid - 1],
name_wrapcls[cid - 1],
)
party = get_party(
profile,
pty_elt[cid - 1],
pty_cls[cid - 1],
pty_wrapelt[cid - 1],
pty_wrapcls[cid - 1],
party_in_main_page,
url,
)

name = party = ''
try:
name = get_name(profile, args.name_elt, args.name_cls,
args.name_wrapelt, args.name_wrapcls)
except Exception as e:
raise RuntimeError(
'[basic.py] 의원 이름을 가져오는데 실패했습니다. 이유 : ' + str(e))
try:
party = get_party(profile, args.pty_elt, args.pty_cls,
args.pty_wrapelt, args.pty_wrapcls, args.pty_wraptxt, url)
except Exception as e:
try:
party = get_party_easy(
profile, args.pty_wrapelt, args.pty_wrapcls, args.pty_wraptxt, url)
except Exception:
raise RuntimeError(
'[basic.py] 의원 정당을 가져오는데 실패했습니다. 이유: ' + str(e))
councilors.append(Councilor(name=name, party=party))

return ScrapResult(
Expand All @@ -143,6 +177,7 @@ def scrap_basic(url, cid, encoding="utf-8") -> ScrapResult:
councilors=councilors,
)


if __name__ == "__main__":
print(scrap_basic("https://www.yscl.go.kr/kr/member/name.do", 3)) # 서울 용산구
if __name__ == '__main__':
args3 = ScrapBasicArgument(
pf_elt='div', pf_cls='profile', name_elt='em', name_cls='name', pty_elt='em')
print(scrap_basic('https://www.yscl.go.kr/kr/member/name.do', 3, args3)) # 서울 용산구
6 changes: 6 additions & 0 deletions scrap/local_councils/gangwon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from urllib.parse import urlparse
import re

from scrap.utils.types import CouncilType, Councilor, ScrapResult, ScrapBasicArgument
from scrap.utils.requests import get_soup
from scrap.local_councils.basic import scrap_basic
5 changes: 5 additions & 0 deletions scrap/local_councils/gwangju.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""광주광역시를 스크랩. 60-64번째 의회까지 있음.
"""
from scrap.utils.types import CouncilType, Councilor, ScrapResult
from scrap.utils.requests import get_soup
from scrap.local_councils.basic import *
Loading

0 comments on commit c1562e9

Please sign in to comment.