需求
从指定网站中获取不同分类下的所有国自然项目数据,如优青、杰青、面上、重大等分类中的项目数据。
依赖包
代码
整个过程分为数据获取和数据处理两部分,具体代码如下。
1.网页原始数据获取
import urllib3import timefrom urllib.parse import unquotedef download_content(url): ''' 根据链接下载数据 :param url: 链接 :return: 页面数据 ''' http = urllib3.PoolManager() # 不登录只能查看20页,所以总页数需要通过查询条件限制在20页以内,建议通过起止时间来限制,便于分类和获取 body = {'startTime': '2016', 'endTime': "2021", 'addcomment_s1': 'D', 'searchsubmit': 'true', 'subcategory': '优秀青年基金项目'} # letpub网站是POST请求,izaiwen网站(已停用)是GET请求 response = http.request('POST', url, fields=body) # 从返回结果中获取页面内容,编码一般是utf-8 response_data = response.data html_content = response_data.decode('utf-8') unquote(html_content) # print(html_content) return html_contentdef save_to_file(filename, content): ''' 保存数据到本地文件 :param filename: 文件名 :param content: 文件内容 :return: 本地文件 ''' fo = open(filename, 'w', encoding='utf-8') fo.write(content) fo.close()if __name__ == '__main__': # url需要根据在letpub网站中点击查询时,控制台中显示的实际地址`Request URL`修改 url_prefix = 'https://www.letpub.com.cn/nsfcfund_search.php?mode=advanced&datakind=list¤tpage=' # 总页数根据网页查询结果调整 page_total = 18 # 保存路径名建议根据查询条件按年份区分 path_prefix = './2016-2021/page-' path_suffix = '.html' # 从第一页开始获取,中间加休眠5~10秒左右,防止访问被禁 page_number = 1 while page_number <= page_total: # 每页的url url = url_prefix + str(page_number) # 每页的原始数据 page_data = download_content(url) # 每页的保存路径 save_path = path_prefix + str(page_number) + path_suffix # 保存每页的数据 save_to_file(save_path, page_data) print('page ' + str(page_number) + ' end') # 休眠10秒 time.sleep(10) # 访问下一个页面 page_number += 1
2.数据处理
import csvdef save_to_file(filename, content): fo = open(filename, "w", encoding="utf-8") fo.write(content) fo.close()def replace_string(content): ''' 页面数据处理方法(根据网站页面内容自定义,以letpub网站举例) :param content: 页面内容 :return: 处理后的页面内容 ''' ''' 页面数据分割的形式,可以打开原始的页面数据文件看看,找易于分割出需要内容的字符串,像letpub网站有下面两行明显标识,很好分割 ''' content = content.split('<!-- ################## start content ################## -->')[1] content = content.split('<!-- ################## end content ################## -->')[0] content = content.split('TR>')[2] ''' 替换页面中重复多次出现的标签 ''' # 此处标签替换为换行符\n是为了区分不同的项目(通常是每页10个项目) content = content.replace('<tr style="background:#EFEFEF;">', '\n') content = content.replace( '<td style="border:1px #DDD solid; border-collapse:collapse; text-align:left; padding:8px 8px 8px 8px;">', '') content = content.replace( 'style="border:1px #DDD solid; border-collapse:collapse; text-align:left; padding:8px 8px 8px 8px; font-size:13px; color:#333333;">', '') content = content.replace( '<td style="border:1px #DDD solid; border-collapse:collapse; text-align:left; padding:8px 8px 8px 8px; font-size:13px; color:#333333;"', '') # 项目小标题对于实际内容获取没有帮助,直接替换掉;若需要保留,可以只替换</td>标签 content = content.replace('题目</td>', '') content = content.replace('学科分类</td>', '') content = content.replace('学科代码</td>', '') content = content.replace('执行时间</td>', '') content = content.replace('中文关键词</td>', '') content = content.replace('英文关键词</td>', '') content = content.replace('结题摘要</td>', '') # 此处的替换字符串@@可以修改为任意其他的可以唯一区分的字符串(如##、#%),不建议使用空格、反斜杠等容易出现内容歧义的字符 content = content.replace('</td>', '@@') content = content.replace('<td', '') content = content.replace( 'style="border:1px #DDD solid; border-collapse:collapse; text-align:left; padding:8px 8px 8px 8px; color:#3b5998; font-weight:bold;">', '') content = content.replace('colspan="6">', '') content = content.replace('</tr>', '') content = content.replace('<tr>', '') ''' 单独处理页面中剩余的非通用标签,如下方的表头 ''' content = content.split('批准年份')[1] content = content.replace('</th>', '') content = content.replace('<', '') content = content.replace('</th>', '') content = content.replace(' ', '') content = content.replace(' ', '') # 返回结果 return contentif __name__ == '__main__': ''' 以下参数需要手动指定,以按年份查询区分为例 ''' # 页面文件的存储路径 origin_path_name = './2016-2021/' # 文件前缀、后准、页面数量 file_name_prefix = 'page-' file_name_suffix = '.html' file_number = 18 # 结果输出的目标路径 target_path_name = './flushed_data/2016-2021/' # 处理后文件的前缀、后缀 target_file_name_prefix = 'flushed-page-' target_file_name_suffix = '.txt' ''' 需要从页面数据中获取的常用的信息 ''' # 负责人 charge_list = [] # 单位 department_list = [] # 金额 (万) money_list = [] # 项目编号 project_number_list = [] # 项目类型 project_type_list = [] # 所属学部 xuebu_list = [] # 批准年份 pass_year_list = [] # 题目 title_list = [] # 学科分类 subject_type_list = [] # 学科代码 subject_code_list = [] # 执行时间 execution_time_list = [] # 记录所有页面中的每一行(因为预处理时不可以替换的换行符\n的存在,会出现空行,通过长度判断筛选掉) lines = [] # 从第1个页面开始处理 page_number = 1 while page_number <= file_number: # 处理后的页面内容 content = '' # 页面文件名 file_name = origin_path_name + file_name_prefix + str(page_number) + file_name_suffix # 打开文件获取数据,按行追加到页面内容中 with open(file_name, 'r+', encoding='utf-8') as f: for line in f: content += line f.close() # 页面数据处理(方法内容自定义,需要根据页面实际内容调整) content = replace_string(content) # print(content) # 保存各处理后的页面内容到文件中 save_path = target_path_name + target_file_name_prefix + str(page_number) + target_file_name_suffix save_to_file(save_path, content) # 打开上一步保存的处理后的页面数据文件 with open(save_path, 'r', encoding='utf-8') as f: # 将符合筛选要求的行内容添加到列表中(因为预处理时不可以替换的换行符\n的存在,会出现空行,通过长度判断筛选掉) for line in f: if (len(line) > 5): line = line.replace('\n', '') lines.append(line) f.close() # 下一个页面 page_number += 1 # 处理列表信息,分到各个单独的列表中 for line in lines: # 可能因为‘结题摘要’等小项的存在,导致出现一些冗余行,先打印结果看看,在循环中加个筛选 if line.count('@@') >= 9: # 页面数据处理方法中人为添加的分隔符做分割,按照网页数据中固定顺序分到不同的列表中 items = line.split('@@') # print(items) charge_list.append(items[0]) department_list.append(items[1]) money_list.append(items[2]) project_number_list.append(items[3]) project_type_list.append(items[4]) xuebu_list.append(items[5]) pass_year_list.append(items[6]) title_list.append(items[7]) subject_type_list.append(items[8]) subject_code_list.append(items[9]) execution_time_list.append(items[10]) # 把上方的列表合并为一个table content_to_table = zip(charge_list, department_list, money_list, project_number_list, project_type_list, xuebu_list, pass_year_list, title_list, subject_type_list, subject_code_list, execution_time_list) # 把数据保存到表格中(csv文件如果用word打开乱码,可以用wps打开) output_table_path = origin_path_name.split('/')[1] + '.csv' with open(output_table_path, 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) for row in content_to_table: writer.writerow(row) f.close()