GitHub
参考:《python3网络爬虫开发实战第二版》——6.3 aiohttp异步爬取
官方文档:aiohttp(客户端)——用于 asyncio 和 Python 的异步 HTTP 客户端/服务器
稍微有点难理解,待回顾
import asyncio
import aiohttp
import logging
import json
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(levelname)s: %(message)s')
INDEX_URL = 'https://spa5.scrape.center/api/book/?limit=18&offset={offset}'
DETAIL_URL = 'https://spa5.scrape.center/api/book/{id}'
PAGE_SIZE = 18 # 页面大小
PAGE_NUMBER = 3 # 页码数量
CONCURRENCY = 5 # 并发量(信号量)
semaphore = asyncio.Semaphore(CONCURRENCY) # 控制并发量
session = None
MONGO_CONNECTION_STRING = 'mongodb://localhost:27017'
MONGO_DB_NAME = 'books'
MONGO_COLLECTION_NAME = 'books'
from motor.motor_asyncio import AsyncIOMotorClient
client = AsyncIOMotorClient(MONGO_CONNECTION_STRING)
db = client[MONGO_DB_NAME]
collection = db[MONGO_COLLECTION_NAME]
# 向url发送请求返回json数据
async def scrape_api(url):
async with semaphore: # 引入信号量作为上下文
try:
logging.info('scraping %s', url)
async with session.get(url) as response: # 发送请求 和requests使用方法类似
return await response.json() # 返回json数据
except aiohttp.ClientError:
logging.error('error occurred while scraping %s', url, exc_info=True)
# 爬取列表页
async def scrape_index(page):
url = INDEX_URL.format(offset=PAGE_SIZE * (page - 1))
return await scrape_api(url)
# 爬取详情页
async def scrape_detail(id):
url = DETAIL_URL.format(id=id)
data = await scrape_api(url)
await save_data(data)
# 保存数据
async def save_data(data):
logging.info('saving data %s', data)
if data:
return await collection.update_one({'id': data.get('id')}, {'$set': data}, upsert=True)
async def main():
global session
session = aiohttp.ClientSession() # 定义客户端会话
# 定义任务列表 列表页
'''
asyncio.ensure_future 定义task对象
'''
scrape_index_tasks = [asyncio.ensure_future(scrape_index(page)) for page in range(1, PAGE_NUMBER + 1)]
json_data = await asyncio.gather(*scrape_index_tasks)
# logging.info('results %s', json.dumps(json_data, ensure_ascii=False, indent=2))
ids = []
for index_data in json_data:
if not index_data: continue
for item in index_data.get('results'):
ids.append(item.get('id'))
# 详情页
scrape_detail_tasks = [asyncio.ensure_future(scrape_detail(id)) for id in ids]
await asyncio.wait(scrape_detail_tasks)
await session.close()
if __name__ == '__main__':
loop=asyncio.get_event_loop() # 定义事件循环
loop.run_until_complete(main()) # 执行直到完成
# asycio.run(main()) # python 3.7+ 可以代替前面两行
2021-12-25 17:08:26,605 - INFO: scraping https://spa5.scrape.center/api/book/?limit=18&offset=0
2021-12-25 17:08:26,633 - INFO: scraping https://spa5.scrape.center/api/book/?limit=18&offset=18
2021-12-25 17:08:26,634 - INFO: scraping https://spa5.scrape.center/api/book/?limit=18&offset=36
2021-12-25 17:08:27,629 - INFO: scraping https://spa5.scrape.center/api/book/7952978
2021-12-25 17:08:27,630 - INFO: scraping https://spa5.scrape.center/api/book/7916054
2021-12-25 17:08:27,630 - INFO: scraping https://spa5.scrape.center/api/book/7698729
2021-12-25 17:08:27,631 - INFO: scraping https://spa5.scrape.center/api/book/7658805
2021-12-25 17:08:27,631 - INFO: scraping https://spa5.scrape.center/api/book/7564736
2021-12-25 17:08:28,130 - INFO: saving data {'id': '7916054', 'comments': [{'id': '816731548', 'content': '云烟过眼,从来无常,绿茵浓浓,如月吩咐,我操这个心做什么?'}, {'id': '875928217', 'content': '精致,有浮生六记的味道。《也谈文艺与复兴》一篇比较特别。'}, {'id': '943342029', 'content': '董桥的文字,我喜欢。'}, {'id': '853751777', 'content': '看的第一本董桥,我想说的是,这种小包装的书真是好舒服,我要买几本其它人的,董桥的就算了。'}, {'id': '2162670094', 'content': '老牌文人的回忆录,故纸堆的写作要读得适量,不然会很暮气沉沉。'}, {'id': '1994143437', 'content': '寡淡得有些过份。'}, {'id': '1989694262', 'content': '就是爱读董桥,八卦也多'}, {'id': '1900797237', 'content': '呵护记忆,体贴遗憾。古稀老人追忆去岁年华,老友种种,终是遗憾。谈及文艺复兴,说不如破开,我修我的文艺,你唱你的复兴。'}, {'id': '1886564872', 'content': '千番世味家风,字字清白好。'}], 'name': '清白家风', 'authors': ['\n 董桥', '海豚简装'], 'translators': [], 'publisher': '海豚出版社', 'tags': ['董桥', '散文', '随笔', '*北京·海豚出版社*', '语言随笔杂感', '中国文学', '海豚出版社', '文学'], 'url': 'https://book.douban.com/subject/7916054/', 'isbn': '9787511007230', 'cover': 'https://img9.doubanio.com/view/subject/l/public/s27250764.jpg', 'page_number': 127, 'price': '15.00', 'score': '7.5', 'introduction': '', 'catalog': '\n 小记\n 无灯无月何妨(l}\n 那些名字那些人(6)\n 杨花满路春归了((11)\n 胡适还是回台湾好(16)\n 想起傅先生(23)\n 溥先生的杖头小手卷(28\n 拜访兰香玉(33 )\n 扇子有情(38)\n 坚道有个管先生(43)\n 写给刘若英的新书(49)\n 西园一枝(54)\n 记戴立克(59)\n 李子不甜(65)\n 钱穆字幅的联想(71)\n 伤逝(76 )\n 清白家风(81)\n 妮香记(87 )\n 张秀本色(93 )\n 也谈文艺与复兴(99 )\n 书香(110 )\n 在春风里(119 )\n · · · · · · ', 'published_at': '2014-04-20T16:00:00Z', 'updated_at': '2020-03-21T16:50:24.831994Z'}
2021-12-25 17:08:28,131 - INFO: scraping https://spa5.scrape.center/api/book/7440370
2021-12-25 17:08:29,028 - INFO: saving data {'id': '7698729', 'comments': [{'id': '542102072', 'content': '文笔不行'}, {'id': '1553406002', 'content': '。。精彩的故事第一部已经讲完了 到最后纯靠zuo戏扯完全篇。烂尾典范'}, {'id': '620040204', 'content': '就是抄筱原千绘的,但不好看。'}, {'id': '521875368', 'content': '结局太狗尾了。。。'}, {'id': '723741755', 'content': '高中的时候好喜欢看穿越小说的 哈哈哈哈哈哈'}, {'id': '2286825971', 'content': '还算圆满了哈哈哈哈。'}, {'id': '2185394006', 'content': '小学看的'}, {'id': '2273277712', 'content': '很喜欢《法老的宠妃》,再会亦不忘却往生——'}, {'id': '2164780482', 'content': '呵呵(;一_一)'}, {'id': '2162362560', 'content': '虽然狗血 但这样的题材真的很戳我'}], 'name': '法老的宠妃 终结篇(上下册)', 'authors': ['\n 悠世', '法老的宠妃'], 'translators': [], 'publisher': '江苏文艺出版社', 'tags': ['法老的宠妃', '穿越', '埃及', '小说', '悠世', '言情', '爱情', '熬夜的原因'], 'url': 'https://book.douban.com/subject/7698729/', 'isbn': '9787539947143', 'cover': 'https://img1.doubanio.com/view/subject/l/public/s7027218.jpg', 'page_number': 512, 'price': '45.00元', 'score': '7.2', 'introduction': '', 'catalog': '\n 上册\n 第一章 艾薇的决心\n 第二章 孤独的假面\n 第三章 提雅男爵\n 第四章 缘起\n 第五章 因果\n 第六章 男爵宅邸\n 第七章 二百三十八年\n 第八章 与那萨尔的相遇\n 第九章 代尔麦地那\n 第十章 热风\n 第十一章 她的身份\n 第十二章 转生\n 第十三章 帝王的心\n 第十四章 艾薇公主的回归\n 第十五章厄运的预兆\n 第十六章 可米托尔\n 第十七章 尤阿拉斯礼冠\n 第十八章 画像\n 第十九章 神秘的使者\n 第二十章 危险的逼近\n 第二十一章 合作\n 第二十二章 分歧\n 第二十三章 拉玛之死\n 第二十四章 层层逼近\n 第二十五章 暗夜的再会\n 第二十六章 时空的复制品\n 第二十七章 爱情的痕迹\n 第二十八章 前往亚述的冒险\n 第二十九章 再会那萨尔\n 下册\n 第三十章 记忆的碎片\n 第三十一章 承诺\n 第三十二章 埃及的厚礼\n 第三十三章 薇\n 第三十四章 他的意图\n 第三十五章 真相\n 第三十六章 沧海桑田\n 第三十七章 用心\n 第三十八章 宿命前夜\n 第三十九章 宿命I\n 第四十章 宿命II\n 第四十一章 后来\n 最终章 再会亦不忘却往生\n 《法老的宠妃 前传》\n 番外 三日王后\n 番外 曙光\n · · · · · · ', 'published_at': '2012-01-20T16:00:00Z', 'updated_at': '2020-03-21T16:56:03.423831Z'}