def parse_cite_list(soup: BeautifulSoup, uid: str): logger = Logger() # 로기 link = '' # 추가 연도별 tc data가 있는 페이지 링크 tc_data = {} # 연도별 tc data # tc_paper_data = [] # TAG 정의 # TAG = { 'PUB_YEAR_LINK': 'a#PublicationYear', 'PUB_YEARS': 'div#PublicationYear_tr' } # TAG 정의 끝 # # 예외 페이지 처리 # if (re.search(r'None of the Citing Articles are in your subscription', soup.text, re.I)): raise exceptions.CiteListNoSubsError() # 예외 페이지 처리 끝 # # FAST5000 데이터 다운로드 # # 현재는 생략한다. # FAST5000 데이터 다운로드 끝# # 더 많은 연도를 조회할 필요가 있는 지 확인 # logger.log('info', 'CITE_LIST//[%s] 더 많은 연도를 조회할 필요가 있는 지 확인' % uid) pub_year_link = soup.select_one(TAG['PUB_YEAR_LINK']) if pub_year_link: link = pub_year_link['href'] return link, tc_data # 더 많은 연도를 조회할 필요가 있는 지 확인 끝 # pub_year_div = soup.select_one(TAG['PUB_YEARS']) regex_cnt_by_year = r'([1-9][0-9]{3}) \(([0-9]+)\)' ms = re.findall(regex_cnt_by_year, pub_year_div.text) for tub in ms: tc_data[tub[0]] = int(tub[1]) logger.log('info', 'CITE_LIST//[%s] DONE' % uid) return link, tc_data
def parse_tc_data(soup: BeautifulSoup, uid: str): logger = Logger() # 로거 tc_data = {} # 연도별 tc data # TAG 정의 # TAG = { 'TC_DATA_TR': 'tr#PublicationYear_raMore_tr', } # TAG 정의 끝 # # TC DATA 파싱 # logger.log('info', 'TC_DATA//[%s] TC DATA 파싱' % uid) raw_tc_data = soup.select_one(TAG['TC_DATA_TR']) regex_cnt_by_year = r'([1-9][0-9]{3}) \(([0-9]+)\)' ms = re.findall(regex_cnt_by_year, raw_tc_data.text) for tub in ms: tc_data[tub[0]] = int(tub[1]) logger.log('info', 'TC_DATA//[%s] DONE' % uid) return tc_data
def cons_callback(ch, method, properties, body): logger = Logger() ''' 바디 메세지 형식 : "type, uid, targetURL, extra" - type : 페이지 타입 (DETAIL, CITE_CNT) - uid : WOS 레코드 uid - targetURL : 목적 파싱 주소 - extra : 기타 인자 ''' ch.basic_ack(delivery_tag=method.delivery_tag) # 로직 시작 # try: ## 파라미터 추출 ## str_body: str = str(body.decode('utf-8')) args: dict = json.loads(str_body) targetType = args['sourceType'] uid = args['UID'] targetURL = args['targetURL'] # extra = args['extra'] mailman = Mailman() parser = ParserInterface(mailman) ## 파라미터 추출 끝 ## logger.log('info', 'Message received "%s" ' % (', '.join(args)[:50])) logger.log('info', 'URL: (%s)' % (targetURL)) ## 파싱 쓰레드 준비 x = threading.Thread(target=parser.run, args=( targetType, uid, targetURL, )) ## 과잉 쓰레딩 방어 (threading.enumerate()은 다른 스레드도 포함함을 주의) logger.log('info', len(threading.enumerate())) while len(threading.enumerate()) > 10: logger.log('info', 'Waiting for other threads, 10sec.') time.sleep(10) ## 쓰레드 시작 logger.log('info', 'Parsing thread will start for "%s". ' % (uid)) x.start() ## 서버 부담을 덜기 위한 기다림 logger.log('info', 'Waiting... 15sec') time.sleep(15) ## 전역 에러 처리 ## except parser_exceptions.LoginRequired as lre: print(lre) traceback.print_exc() except parser_exceptions.NoPaperDataError as npde: print(npde) traceback.print_exc() except Exception as e: print(e) traceback.print_exc() else: logger.log('info', 'Parsing %s ends' % (uid))
except parser_exceptions.LoginRequired as lre: print(lre) traceback.print_exc() except parser_exceptions.NoPaperDataError as npde: print(npde) traceback.print_exc() except Exception as e: print(e) traceback.print_exc() else: logger.log('info', 'Parsing %s ends' % (uid)) ## 전역 에러 끝 ## # 로직 끝 # # 메세지 콜백 정의 끝 # 서버 메인 if __name__ == "__main__": # RabbitMQ 커넥션 설정 # connection = pika.BlockingConnection( pika.URLParameters(RABBITMQ_SERVER_URL)) channel = connection.channel() channel.basic_consume('targetURLs', cons_callback) # 메세지 소비 시작 # Logger().log('info', 'Waiting for targetURLs. To exit press CTRL+C') channel.start_consuming() # 서버 메인 끝
def parse_cite_list(soup: BeautifulSoup, session: requests.Session, uid: str): logger = Logger() # 로기 link = '' # 추가 연도별 tc data가 있는 페이지 링크 tc_data = {} # 연도별 tc data # tc_paper_data = [] # TAG 정의 # TAG = { 'PUB_YEAR_LINK': 'a#PublicationYear', 'PUB_YEARS': 'div#PublicationYear_tr' } # TAG 정의 끝 # # 예외 페이지 처리 # if (re.search(r'None of the Citing Articles are in your subscription', soup.text, re.I)): raise exceptions.CiteListNoSubsError() # 예외 페이지 처리 끝 # # FAST5000 데이터 다운로드 # SID = soup.select('input#SID')[0].attrs['value'] qid = soup.select('input#qid')[0].attrs['value'] rurl = soup.select('input#rurl')[0].attrs['value'] action_url = '/OutboundService.do?action=go&&' form_data = { 'qid': str(qid), 'SID': SID, 'mark_to': '5000', 'markTo': '5000', } form_data = parser_constants.get_form_data(action_url, form_data) url = parser_constants.WOS_BASE_URL + action_url http_res = session.post(url, form_data) # FAST5000 데이터 다운로드 끝 # # FAST5000 데이터 정제 # fast_5000 = http_res.content.decode('utf-8').replace('\r', '') fast_5000_list = fast_5000.split('\n') keys = fast_5000_list[0].split('\t') fast_5000_list = fast_5000_list[1:] if fast_5000_list[-1] == '': fast_5000_list.pop() articles = [] for row in fast_5000_list: article = {} row_list = row.split('\t') for idx, key in enumerate(keys): article[key.lower()] = row_list[idx] published_year = article['py'].strip() if article['py'] else 'unknown' published_date = article['pd'].strip( ) if article['pd'] else published_year published_month = published_date.split()[0] if published_year in tc_data: tc_data[published_year]['total'] += 1 else: tc_data[published_year] = {'total': 1} if published_month in tc_data[published_year]: tc_data[published_year][published_month] += 1 else: tc_data[published_year][published_month] = 1 articles.append(article) # FAST5000 데이터 정제 끝 # # # 더 많은 연도를 조회할 필요가 있는 지 확인 # # logger.log('info', 'CITE_LIST//[%s] 더 많은 연도를 조회할 필요가 있는 지 확인' % uid) # pub_year_link = soup.select_one(TAG['PUB_YEAR_LINK']) # if pub_year_link: # link = pub_year_link['href'] # return link, tc_data # # 더 많은 연도를 조회할 필요가 있는 지 확인 끝 # # pub_year_div = soup.select_one(TAG['PUB_YEARS']) # regex_cnt_by_year = r'([1-9][0-9]{3}) \(([0-9]+)\)' # ms = re.findall(regex_cnt_by_year, pub_year_div.text) # for tub in ms: # tc_data[tub[0]] = int(tub[1]) # logger.log('info', 'CITE_LIST//[%s] DONE' % uid) return link, tc_data, articles
def parse_detail(soup: BeautifulSoup, uid: str): logger = Logger() # 로거 ret_link: str = '' # 대상을 인용 중인 논문 목록 링크 paper_data: dict = { # 논문 정보 'uid': uid, # 논문 아이디 'timesCited': 0, # 인용 횟수 'parsedAuthorList': [], # 저자 리스트 'firstAuthor': {}, # 제 1 저자 'reprint': {}, # 교신 저자 'journalImpact': {}, # 저널 랭크 'grades': [], # 논문 등급 'recordState': '', # 레코드 상태 } # parsedAuthorList 원소 형태 ''' author = { 'name' : '' 'fullName' : '', 'address' : ['', ''] } ''' # grades 원소 형태 ''' { 'fullGrade': '', 'caped': '' } ''' # Tags 정의 # TAG = { 'PAGING_BTN': 'a.paginationNext', 'CITE_CNT_LINK': 'a.snowplow-citation-network-times-cited-count-link', 'RECORD_INFOS': 'div.block-record-info', 'SOURCE_TITLE': 'source_title_txt_label', 'JCR_BLOCK': 'div.overlayJCRblock', 'IF_TABLE': 'table.Impact_Factor_table', 'JCR_TABLE': 'table.JCR_Category_table', 'JCR_TEXT': 'p.overlayJCRtext', 'GRADES': 'div.flex-justify-start > ul > span.box-label', } # Tags 정의 끝 # # 예외 페이지 처리 # logger.log('info', 'DETAIL//[%s] 예외 페이지 처리' % uid) pbtn = soup.select(TAG['PAGING_BTN']) if not pbtn and re.search('Record not available', soup.text, re.I): raise exceptions.RecordNotAvailableError() if not pbtn and re.search('server error', soup.text, re.I): raise exceptions.RecordNotAvailableError() if not pbtn and re.search('Access denied', soup.text, re.I): raise exceptions.AcessDeniedError() if not pbtn: raise exceptions.NoPaperDataError() # 예외 페이지 처리 끝 # # 인용 횟수 및 링크 # logger.log('info', 'DETAIL//[%s] 인용 횟수 및 링크' % uid) cnt_link = soup.select_one(TAG['CITE_CNT_LINK']) if not cnt_link: ret_link = '' paper_data['timesCited'] = 0 else: ret_link = cnt_link['href'] paper_data['timesCited'] = int(cnt_link.span.text) # 인용 횟수 및 링크 끝 # # 저자 및 주소 # logger.log('info', 'DETAIL//[%s] 저자 및 주소' % uid) authors = [] reprint_name = '' address_map = {} raw_author_by = '' raw_author_infos = '' record_infos = soup.select(TAG['RECORD_INFOS']) target_flag = 0 for info in record_infos: if target_flag is 2: break # 저자 if info.text.find('By:') != -1: target_flag += 1 raw_author_by = info.text.split(':')[1] # 주소 if info.text.find('Author Information') != -1: target_flag += 1 raw_author_infos = info.text regex_sp = r'\n|( )|( )|(\\xa0)|(\t)' raw_author_by = re.sub(regex_sp, '', raw_author_by, flags=re.M) raw_author_infos = re.sub(regex_sp, '', raw_author_infos, flags=re.M) ## 원본 주소 데이터 정제 ## logger.log('info', 'DETAIL//[%s] 원본 주소 데이터 정제' % uid) ptn_reprint = re.compile( r'.+Reprint Address:+(?P<name>[A-Z,a-z.\'\- ]+)\(reprint author\)(?P<address>[A-Z,a-z.\'\- 0-9&]+)?' ) ptn_address = re.compile( r'\[ (?P<address_key>\d+) \] (?P<address>[A-Za-z,.\'\- 0-9&]+)') m = ptn_reprint.match(raw_author_infos) if m: reprint_name = m.group('name').strip() if m.group('name') else None reprint_address = m.group('address').strip() if m.group( 'address') else None else: reprint_name = None reprint_address = None for address_tup in ptn_address.findall(raw_author_infos): address_map[address_tup[0].strip()] = address_tup[1].strip() ## 원본 주소 데이터 정제 끝 ## ## 원본 저자 데이터 정제 ## logger.log('info', 'DETAIL//[%s] 원본 저자 데이터 정제' % uid) '''By:Zhou, B (Zhou, Bo)[1 ]; Xu, YP (Xu, Yanping)[2 ]; Lee, SK (Lee, Seul Ki)[3,4 ]''' ptn_author_line = re.compile( r'(?P<name>[A-Z,a-z.\'\- ]+) (\((?P<full_name>[A-Z,a-z.\'\- ]+)\))?(\[(?P<address_keys>[0-9,\' ]+)\])?' ) for raw_author in raw_author_by.split(';'): m = ptn_author_line.match(raw_author.strip()) author = { 'name': m.group('name').strip(), 'fullName': m.group('full_name').strip() if m.group('full_name') else '', 'address': [], } address_keys = m.group('address_keys') if not address_keys: address_keys = ['1'] if address_map else [] else: address_keys = address_keys.split(',') for key in address_keys: key = key.strip() author['address'].append(address_map[key]) # 교신 저자 정보가 없을 경우, 제 1저자가 교신 저자이다. if reprint_name and reprint_name == author['name']: if not author['address'] and reprint_address: author['address'] += [reprint_address] paper_data['reprint'] = author authors.append(author) if not reprint_name and authors: paper_data['reprint'] = authors[0] # paper_data['reprint'] = paper_data['reprint']['name'] paper_data['firstAuthor'] = authors[0] paper_data['parsedAuthorList'] = authors ## 원본 저자 데이터 정제 끝 ## # 저자 및 주소 끝 # # JCR # sourceTitle = soup.select(TAG['SOURCE_TITLE']) jcr_block = soup.select(TAG['JCR_BLOCK']) journalImpact = { 'sourceTitle': sourceTitle[0].text.strip() if sourceTitle else None, 'impactFactorByYear': {}, 'jcrDataByYear': {} } if_table = None jcr_tables = None jcr_texts = None if jcr_block: if_table = soup.select(TAG['IF_TABLE']) jcr_tables = soup.select(TAG['JCR_TABLE']) jcr_texts = soup.select(TAG['JCR_TEXT']) if if_table: if_table = if_table[0] tds = if_table.find_all('td') ths = if_table.find_all('th') for idx in range(len(ths)): td = tds[idx] th = ths[idx] journalImpact['impactFactorByYear'][ th.text.strip()] = td.text.strip() if jcr_tables: for idx, jcr_table in enumerate(jcr_tables): year = re.search(r'\d+', jcr_texts[idx].text) year = year[0] if year else 'unknown' journalImpact['jcrDataByYear'][year] = { 'category': [], 'rankInCategory': [], 'quartileInCategory': [] } trs = jcr_table = jcr_table.find_all('tr') for tr in trs[1:]: tds = tr.find_all('td') if tds[0]: journalImpact['jcrDataByYear'][year]['category'].append( tds[0].text.strip()) if tds[1]: journalImpact['jcrDataByYear'][year][ 'rankInCategory'].append(tds[1].text.strip()) if tds[2]: journalImpact['jcrDataByYear'][year][ 'quartileInCategory'].append(tds[2].text.strip()) paper_data['journalImpact'] = journalImpact # JCR 끝 # # 등급 # logger.log('info', 'DETAIL//[%s] 등급' % uid) grades = [] labels = soup.select(TAG['GRADES']) for label in labels: full_grade = label.text.replace('- ', '').strip() caped = re.sub(r'[ a-z]+', r'', full_grade) # grades += [{'fullGrade': full_grade, 'caped': caped}] grades += [caped] paper_data['grades'] = grades # 등급 끝 # logger.log('info', 'DETAIL//[%s] DONE' % uid) return (ret_link, paper_data)
def __init__(self, parser_id=None): self.parser_id = parser_id if parser_id else os.getpid() self.es_time = 0 self.logger = Logger() self.connection = None self.channel = None
def parse_detail(soup: BeautifulSoup, uid: str): logger = Logger() # 로거 ret_link: str = '' # 대상을 인용 중인 논문 목록 링크 paper_data: dict = { # 논문 정보 'uid': uid, # 논문 아이디 'timesCited': 0, # 인용 횟수 'authors': [], # 저자 리스트 'firstAuthor': {}, # 제 1 저자 'reprint': {}, # 교신 저자 'jcr': {}, # 저널 랭크 'grades': [], # 논문 등급 'recordState': '' # 레코드 상태 } # authors 원소 형태 ''' author = { 'name' : '' 'fullName' : '', 'addresses' : ['', ''] } ''' # grades 원소 형태 ''' { 'fullGrade': '', 'caped': '' } ''' # Tags 정의 # TAG = { 'PAGING_BTN': 'a.paginationNext', 'CITE_CNT_LINK': 'a.snowplow-citation-network-times-cited-count-link', 'RECORD_INFOS': 'div.block-record-info', 'JCR_TABLE': 'table.JCR_Category_table', 'GRADES': 'div.flex-justify-start > ul > span.box-label', } # Tags 정의 끝 # # 예외 페이지 처리 # logger.log('info', 'DETAIL//[%s] 예외 페이지 처리' % uid) pbtn = soup.select(TAG['PAGING_BTN']) if not pbtn and re.search('Record not available', soup.text, re.I): raise exceptions.RecordNotAvailableError() if not pbtn and re.search('server error', soup.text, re.I): raise exceptions.RecordNotAvailableError() if not pbtn and re.search('Access denied', soup.text, re.I): raise exceptions.AcessDeniedError() if not pbtn: raise exceptions.NoPaperDataError() # 예외 페이지 처리 끝 # # 인용 횟수 및 링크 # logger.log('info', 'DETAIL//[%s] 인용 횟수 및 링크' % uid) cnt_link = soup.select_one(TAG['CITE_CNT_LINK']) if not cnt_link: ret_link = '' paper_data['timesCited'] = 0 else: ret_link = cnt_link['href'] paper_data['timesCited'] = int(cnt_link.span.text) # 인용 횟수 및 링크 끝 # # 저자 및 주소 # logger.log('info', 'DETAIL//[%s] 저자 및 주소' % uid) authors = [] reprint_name = '' address_map = {} raw_author_by = '' raw_author_infos = '' record_infos = soup.select(TAG['RECORD_INFOS']) target_flag = 0 for info in record_infos: if target_flag is 2: break # 저자 if info.text.find('By:') != -1: target_flag += 1 raw_author_by = info.text.split(':')[1] # 주소 if info.text.find('Author Information') != -1: target_flag += 1 raw_author_infos = info.text regex_sp = r'\n|( )|( )|(\\xa0)|(\t)' raw_author_by = re.sub(regex_sp, '', raw_author_by, flags=re.M) raw_author_infos = re.sub(regex_sp, '', raw_author_infos, flags=re.M) ## 원본 주소 데이터 정제 ## logger.log('info', 'DETAIL//[%s] 원본 주소 데이터 정제' % uid) ptn_reprint = re.compile( r'.+Reprint Address:+(?P<name>[A-Z,a-z.\'\- ]+)\(reprint author\)(?P<address>[A-Z,a-z.\'\- 0-9&]+)?' ) ptn_address = re.compile( r'\[ (?P<address_key>\d+) \] (?P<address>[A-Za-z,.\'\- 0-9&]+)') m = ptn_reprint.match(raw_author_infos) if m: reprint_name = m.group('name').strip() if m.group('name') else None reprint_address = m.group('address').strip() if m.group( 'address') else None else: reprint_name = None reprint_address = None for address_tup in ptn_address.findall(raw_author_infos): address_map[address_tup[0].strip()] = address_tup[1].strip() ## 원본 주소 데이터 정제 끝 ## ## 원본 저자 데이터 정제 ## logger.log('info', 'DETAIL//[%s] 원본 저자 데이터 정제' % uid) '''By:Zhou, B (Zhou, Bo)[1 ]; Xu, YP (Xu, Yanping)[2 ]; Lee, SK (Lee, Seul Ki)[3,4 ]''' ptn_author_line = re.compile( r'(?P<name>[A-Z,a-z.\'\- ]+) (\((?P<full_name>[A-Z,a-z.\'\- ]+)\))?(\[(?P<address_keys>[0-9,\' ]+)\])?' ) for raw_author in raw_author_by.split(';'): m = ptn_author_line.match(raw_author.strip()) author = { 'name': m.group('name').strip(), 'fullName': m.group('full_name').strip() if m.group('full_name') else '', 'addresses': [], 'reprint': False } address_keys = m.group('address_keys') if not address_keys: address_keys = ['1'] if address_map else [] else: address_keys = address_keys.split(',') for key in address_keys: key = key.strip() author['addresses'].append(address_map[key]) # 교신 저자 정보가 없을 경우, 제 1저자가 교신 저자이다. if reprint_name and reprint_name == author['name']: if not author['addresses'] and reprint_address: author['addresses'] += [reprint_address] author['reprint'] = True paper_data['reprint'] = author authors.append(author) if not reprint_name and authors: authors[0]['reprint'] = True paper_data['reprint'] = authors[0] paper_data['firstAuthor'] = authors[0] paper_data['authors'] = authors ## 원본 저자 데이터 정제 끝 ## # 저자 및 주소 끝 # # JCR # # 일단 생략한다. # JCR 끝 # # 등급 # logger.log('info', 'DETAIL//[%s] 등급' % uid) grades = [] labels = soup.select(TAG['GRADES']) for label in labels: full_grade = label.text.replace('- ', '').strip() caped = re.sub(r'[ a-z]+', r'', full_grade) grades += [{'fullGrade': full_grade, 'caped': caped}] paper_data['grades'] = grades # 등급 끝 # logger.log('info', 'DETAIL//[%s] DONE' % uid) return (ret_link, paper_data)