diff --git a/B 高性能模式/000 普通做法.py b/B 高性能模式/000 普通做法.py deleted file mode 100644 index 484f740..0000000 --- a/B 高性能模式/000 普通做法.py +++ /dev/null @@ -1,67 +0,0 @@ -""" - 此方法为普通做法,即使用requests库通过Post请求爬取网页内容,再使用json提取新闻内容。 - 注意:本代码中的关键词列表默认为['灾害'],日期范围默认为2018年1月1日至2018年12月31日。 - Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - Examples: - ``` - main(keywords=['灾害'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10) - ``` -""" -import util -import logging -from typing import List - -import tqdm - - -@util.timeit -def main(keywords: List[str], begin_date: str, end_date: str, size: int = 10): - """ - 爬取与提供的关键词列表相关的新闻. - - Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - """ - logging.basicConfig(level=logging.INFO, - format="%(asctime)s - %(levelname)s - %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", - filename='log.txt', - encoding='utf-8') - - logging.info("开始运行普通爬取") - - spider = util.Spider( keywords=keywords, - begin_date=begin_date, - end_date=end_date, - size=size) - - pbar = tqdm.tqdm(total=size * 10, desc='普通爬取进度', unit='条', ncols=80) - title_list = [] - for keyword in keywords: - for current in range(1, 11): - logging.info(f'keyword: {keyword}, current: {current}') - config = spider.get_config(keyword, current) - data = spider.fetch(config) - title_list += spider.parse(data) - pbar.update(size) - - spider.save(title_list) - pbar.close() - logging.info("爬取完成") - - -if __name__ == "__main__": - main(keywords=['经济'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10) \ No newline at end of file diff --git a/B 高性能模式/010 多进程.py b/B 高性能模式/010 多进程.py deleted file mode 100644 index 94177bc..0000000 --- a/B 高性能模式/010 多进程.py +++ /dev/null @@ -1,86 +0,0 @@ -""" -根据提供的关键词列表,爬取天水市人民政府网站上指定日期内与关键词相关的新闻的标题,并将其存储至数据库中。 - -考虑到相关因素,因此本代码只爬取前10页的新闻内容,即最多100条新闻作为测试。 - -此方法为多进程做法,即使用多进程并发爬取网页内容,再使用json提取新闻内容。 - -注意:本代码中的关键词列表默认为['灾害'],日期范围默认为2018年1月1日至2018年12月31日。 - -Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - -Examples: - ``` - main(keywords=['灾害'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10) - ``` -""" -import util -import logging -from typing import List -import multiprocessing - -import tqdm - -lock = multiprocessing.Lock() - - -@util.timeit -def main(keywords: List[str], begin_date: str, end_date: str, size: int = 10): - """ - 爬取与提供的关键词列表相关的新闻. - - Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - """ - logging.basicConfig(level=logging.INFO, - format="%(asctime)s - %(levelname)s - %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", - filename='log.txt', - encoding='utf-8') - - logging.info("开始运行普通做法") - - spider = util.Spider(keywords=keywords, - begin_date=begin_date, - end_date=end_date, - size=size) - - title_list = [] - pbar = tqdm.tqdm(total=size * 10, desc='多进程爬取进度', unit='条', ncols=80) - - with multiprocessing.Pool(processes=5) as pool: - results = [] - for keyword in keywords: - for current in range(1, 11): - logging.info(f'keyword: {keyword}, current: {current}') - config = spider.get_config(keyword, current) - results.append(pool.apply_async(spider.fetch, (config, ))) - - for result in results: - data = result.get() - title_list += spider.parse(data) - - lock.acquire() - pbar.update(size) - lock.release() - - spider.save(title_list) - pbar.close() - logging.info("爬取完成") - - -if __name__ == "__main__": - main(keywords=['灾害'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10) diff --git a/B 高性能模式/020 多线程.py b/B 高性能模式/020 多线程.py deleted file mode 100644 index 19bc3c4..0000000 --- a/B 高性能模式/020 多线程.py +++ /dev/null @@ -1,89 +0,0 @@ -""" -根据提供的关键词列表,爬取天水市人民政府网站上指定日期内与关键词相关的新闻的标题,并将其存储至数据库中。 - -考虑到相关因素,因此本代码只爬取前10页的新闻内容,即最多100条新闻作为测试。 - -此方法为多线程做法,即使用多线程并行爬取网页内容,再使用json提取新闻内容。 - -注意:本代码中的关键词列表默认为['灾害'],日期范围默认为2018年1月1日至2018年12月31日。 - -Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - -Examples: - ``` - main(keywords=['灾害'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10) - ``` -""" - -from concurrent.futures import ThreadPoolExecutor, as_completed -import threading -import util -import logging -from typing import List - -import tqdm - -lock = threading.Lock() - - -@util.timeit -def main(keywords: List[str], begin_date: str, end_date: str, size: int = 10): - """ - 爬取与提供的关键词列表相关的新闻. - - Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - """ - logging.basicConfig(level=logging.INFO, - format="%(asctime)s - %(levelname)s - %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", - filename='log.txt', - encoding='utf-8') - - logging.info("开始运行多线程爬取") - - spider = util.Spider(keywords=keywords, - begin_date=begin_date, - end_date=end_date, - size=size) - - pbar = tqdm.tqdm(total=size * 10, desc='多线程爬取进度', unit='条', ncols=80) - title_list = [] - tasks = [] - with ThreadPoolExecutor(max_workers=5) as executor: - for keyword in keywords: - for current in range(1, 11): - logging.info(f'keyword: {keyword}, current: {current}') - - config = spider.get_config(keyword, current) - future = executor.submit(spider.fetch, config) - tasks.append(future) - # 更新进度条 - lock.acquire() - pbar.update(size) - lock.release() - - for future in as_completed(tasks): - data = future.result() - title_list += spider.parse(data) - - spider.save(title_list) - pbar.close() - logging.info("爬取完成") - - -if __name__ == "__main__": - main(keywords=['灾害'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10) diff --git a/B 高性能模式/030 协程.py b/B 高性能模式/030 协程.py deleted file mode 100644 index 291736f..0000000 --- a/B 高性能模式/030 协程.py +++ /dev/null @@ -1,89 +0,0 @@ -""" -根据提供的关键词列表,爬取天水市人民政府网站上指定日期内与关键词相关的新闻的标题,并将其存储至数据库中。 - -考虑到相关因素,因此本代码只爬取前10页的新闻内容,即最多100条新闻作为测试。 - -此方法为协程做法,即使用gevent库通过协程并发爬取网页内容,再使用json提取新闻内容。 - -注意:本代码中的关键词列表默认为['灾害'],日期范围默认为2018年1月1日至2018年12月31日。 - -Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - -Examples: - ``` - main(keywords=['灾害'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10) - ``` -""" - -import gevent -from gevent import monkey - -# 打补丁,使标准库能够与gevent协同工作 -monkey.patch_all() - -import util -import logging -from typing import List - -import tqdm - - -@util.timeit -def main(keywords: List[str], begin_date: str, end_date: str, size: int = 10): - """ - 爬取与提供的关键词列表相关的新闻. - - Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - """ - logging.basicConfig(level=logging.INFO, - format="%(asctime)s - %(levelname)s - %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", - filename='log.txt', - encoding='utf-8') - - logging.info("开始运行协程爬取") - - spider = util.Spider(keywords=keywords, - begin_date=begin_date, - end_date=end_date, - size=size) - - pbar = tqdm.tqdm(total=size * 10, desc='协程爬取进度', unit='条', ncols=80) - title_list = [] - - def fetch_and_parse(keyword, current): - logging.info(f'keyword: {keyword}, current: {current}') - config = spider.get_config(keyword, current) - data = spider.fetch(config) - titles = spider.parse(data) - title_list.extend(titles) - pbar.update(size) - - jobs = [ - gevent.spawn(fetch_and_parse, keyword, current) for keyword in keywords - for current in range(1, 11) - ] - - gevent.joinall(jobs) - - spider.save(title_list) - pbar.close() - logging.info("爬取完成") - - -if __name__ == "__main__": - main(keywords=['灾害'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10) diff --git a/B 高性能模式/040 异步.py b/B 高性能模式/040 异步.py deleted file mode 100644 index fb2b1c1..0000000 --- a/B 高性能模式/040 异步.py +++ /dev/null @@ -1,85 +0,0 @@ -""" -根据提供的关键词列表,爬取天水市人民政府网站上指定日期内与关键词相关的新闻的标题,并将其存储至数据库中。 - -考虑到相关因素,因此本代码只爬取前10页的新闻内容,即最多100条新闻作为测试。 - -此方法为多线程做法,即使用异步并行爬取网页内容,再使用json提取新闻内容。 - -注意:本代码中的关键词列表默认为['灾害'],日期范围默认为2018年1月1日至2018年12月31日。 - -Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - -Examples: - ``` - asyncio.run( - main_async(keywords=['灾害'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10)) - ``` -""" - -import asyncio -import util -import logging -from typing import List -import tqdm - - -@util.timeit_async -async def main_async(keywords: List[str], - begin_date: str, - end_date: str, - size: int = 10): - """ - 使用异步方式爬取与提供的关键词列表相关的新闻. - - Args: - keywords: 用于搜索新闻的关键词列表 - begin_date: 开始日期,用于搜索 - end_date: 结束日期,用于搜索 - size: 一次请求返回的新闻或政策的最大数量 - """ - logging.basicConfig(level=logging.INFO, - format="%(asctime)s - %(levelname)s - %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", - filename='log.txt', - encoding='utf-8') - - logging.info("开始运行异步爬取") - - spider = util.Spider(keywords=keywords, - begin_date=begin_date, - end_date=end_date, - size=size) - - pbar = tqdm.tqdm(total=size * 10, desc='异步爬取进度', unit='条', ncols=80) - title_list = [] - tasks = [] - for keyword in keywords: - for current in range(1, 11): - logging.info(f'keyword: {keyword}, current: {current}') - config = spider.get_config(keyword, current) - task = asyncio.create_task(spider.fetch_async(config)) - tasks.append(task) - - for task in asyncio.as_completed(tasks): - data = await task - title_list += spider.parse(data) - pbar.update(size) - - spider.save(title_list) - pbar.close() - logging.info("爬取完成") - - -if __name__ == "__main__": - asyncio.run( - main_async(keywords=['灾害'], - begin_date='2018-01-01', - end_date='2018-12-31', - size=10)) diff --git a/B 高性能模式/readme.md b/B 高性能模式/readme.md deleted file mode 100644 index fee0880..0000000 --- a/B 高性能模式/readme.md +++ /dev/null @@ -1,28 +0,0 @@ - - -# 目标 -本节使用一个爬虫任务来展示如何追求代码的性能 。 -充分理解线程、协程、进程、同步、异步、阻塞、非阻塞等概念,并能够根据具体场景选择合适的并发模型。 -主线问题:如何解决IO和计算速度不匹配、如何任务分解、分发和协作 。 - -# 任务 - -根据提供的关键词列表,爬取天水市人民政府网站上指定日期内与关键词相关的新闻的标题,并将其存储至数据库中。 -本代码只爬取前10页的新闻内容,即最多100条新闻作为测试。 - - -# 讨论分析 -普通做法连续进行了五次测试,时间分别为34.231s、34.091s、34.164s、34.226s、33.958s,平均时间为34.134s -多进程(进程数=5)连续进行了五次测试,时间分别为7.719s、7.716s、7.690s、7.730s、7.711s,平均时间为7.7132s -多线程(线程数=5)连续进行了五次测试,时间分别为7.185s、7.964s、6.983s、6.969s、7.035s,平均时间为7.2272s -协程连续进行了五次测试,时间分别为3.775s、3.807s、3.733s、3.824s、3.744s,平均时间为3.776s -异步连续进行了五次测试,时间分别为6.975s、7.675s、7.018s、7.032s、7.049s,平均时间为7.1498s -注:为保证公平性,每一次Post请求后休眠3秒 - -可以看出,协程的性能最好,普通做法的性能最差,多线程、多进程和异步的性能介于两者之间。 -考虑到多进程和多线程是故意开的5个进程和线程,而协程是单线程,所以协程的性能最好。 -另外,异步的性能最差,可能是由于异步的并发模型需要频繁地切换线程,导致性能下降。 -总的来说,协程的性能最好,多线程和多进程的性能介于两者之间,普通做法的性能最差。 - -# 总结 -协程的性能最好,多线程和多进程的性能介于两者之间,普通做法的性能最差。 \ No newline at end of file diff --git a/B 高性能模式/util.py b/B 高性能模式/util.py deleted file mode 100644 index 88e1655..0000000 --- a/B 高性能模式/util.py +++ /dev/null @@ -1,185 +0,0 @@ -import re -import time -import functools -import json -import asyncio -import requests -from typing import Any, Dict, List - - -class Spider: - """ - 爬虫类 - - Args: - keywords (List[str]): 用于搜索新闻的关键词列表 - begin_date (str): 开始日期,用于搜索 - end_date (str): 结束日期,用于搜索 - size (int): 一次请求返回的新闻或政策的最大数量 - - Attributes: - URL (str): 网址 - """ - # 天水市人民政府网站 - URL = ('https://www.tianshui.gov.cn/aop_component/' - '/webber/search/search/search/queryPage') - - def __init__(self, keywords: List[str], begin_date: str, end_date: str, - size: int): - self.keywords = keywords - self.begin_date = begin_date - self.end_date = end_date - self.size = size - - def get_config(self, keyword: str, current: int) -> Dict[str, Any]: - """ - 获取配置信息。 - - Args: - keyword (str): 关键词 - size (int): 一次请求返回的新闻的最大数量 - - Returns: - Dict[str, Any]: 配置信息 - """ - - return { - "aliasName": "article_data,open_data,mailbox_data,article_file", - "keyWord": keyword, - "lastkeyWord": keyword, - "searchKeyWord": False, - "orderType": "score", - "searchType": "text", - "searchScope": "3", - "searchOperator": 0, - "searchDateType": "custom", - "searchDateName": f"{self.begin_date}-{self.end_date}", - "beginDate": self.begin_date, - "endDate": self.end_date, - "showId": "c2ee13065aae85d7a998b8a3cd645961", - "auditing": ["1"], - "owner": "1912126876", - "token": "tourist", - "urlPrefix": "/aop_component/", - "page": { - "current": current, - "size": self.size, - "pageSizes": [2, 5, 10, 20, 50, 100], - "total": 0, - "totalPage": 0, - "indexs": [] - }, - "advance": False, - "advanceKeyWord": "", - "lang": "i18n_zh_CN" - } - - def generate_headers(self) -> dict: - """ - 生成请求头 - Returns: - dict: 请求头 - """ - return { - 'Authorization': - 'tourist', - 'User-Agent': - ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit' - '/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari' - '/537.36 Edg/124.0.0.0') - } - - def fetch(self, config: Dict[str, Any]) -> Dict[str, Any]: - """ - 普通做法。 - Post请求获取网页内容,并返回请求结果。 - - Args: - config (Dict[str, Any]): 配置信息 - - Returns: - Dict[str, Any]: 请求结果 - """ - response = requests.post(self.URL, - headers=self.generate_headers(), - json=config).text - time.sleep(3) - return json.loads(response) - - - async def fetch_async(self, config: Dict[str, Any]) -> Dict[str, Any]: - """ - 异步做法。 - Post请求获取网页内容,并返回请求结果。 - - Args: - config (Dict[str, Any]): 配置信息 - - Returns: - Dict[str, Any]: 请求结果 - """ - response = requests.post(self.URL, - headers=self.generate_headers(), - json=config).text - await asyncio.sleep(3) - return json.loads(response) - - - def parse(self, data: Dict[str, Any]) -> List[str]: - """ - 解析网页内容。 - - Args: - data (Dict[str, Any]): 网页内容 - - Returns: - List[str]: 标题列表 - """ - title_list = [] - records = data['data']['page']['records'] - for i in range(self.size): - title = records[i]['title'] - title = re.sub('<[^>]*>', '', title) # 去除html标签 - title_list.append(title) - # print(title) - return title_list - - - def save(self, title_list: List[str]): - """ - 保存数据。 - """ - pass - - -# 时间装饰器 -def timeit(func): - """ - 计算函数运行时间 - Args: - func: 函数 - Return: - 函数 - """ - - def wrapper(*args, **kwargs): - start = time.time() - result = func(*args, **kwargs) - - print(f'{func.__name__} cost: {time.time() - start}') - return result - - return wrapper - - -def timeit_async(func): - - @functools.wraps(func) - async def wrapper(*args, **kwargs): - start = time.time() - result = await func(*args, **kwargs) - - print(f'{func.__name__} cost: {time.time() - start}') - return result - - return wrapper \ No newline at end of file diff --git a/readme.MD b/readme.MD index 577e868..db24506 100644 --- a/readme.MD +++ b/readme.MD @@ -2,4 +2,4 @@ ## 代码为啥要这样写,我要这样写代码 -执行时间快,内存占用少,类型安全、可读性强、可复用高、维护成本低(测试自动化) +执行时间快,内存占用少,类型安全、可读性强、可复用高、维护成本低(测试自动化)等等理由