spa1_thread 多线程爬虫
使用 Python 进行多线程数据爬取和存储到 MongoDB 的实战案例
一、前言
这一篇同样是spa爬虫练习,关卡(一),使用多线程技术提高数据获取的效率,最后将数据存储到 MongoDB 数据库中。无论你是 Python 初学者还是已经有一定经验的开发者,都可以通过这个案例来学习如何使用 requests 库进行网络请求、使用 concurrent.futures 库进行多线程操作,以及使用 pymongo 库操作 MongoDB 数据库。
二、导入必要的库
1 | import pymongo.errors # 导入 pymongo 的异常类,用于处理 MongoDB 操作时可能出现的异常 |
- pymongo.errors:当我们使用
pymongo库操作 MongoDB 时,可能会出现各种错误,导入这个模块可以帮助我们处理这些异常。 - requests:这个库是 Python 中非常流行的用于发送 HTTP 请求的工具,我们可以使用它来获取网页或 API 的内容。
- time:用来计算程序运行的时间,方便我们评估程序的性能。
- pymongo:提供了操作 MongoDB 数据库的接口,使我们可以存储和检索数据。
- ThreadPoolExecutor 和 as_completed:这两个来自
concurrent.futures库,它们是 Python 中进行多线程编程的强大工具。ThreadPoolExecutor可以创建一个线程池,方便我们管理多个线程;as_completed可以让我们等待线程任务完成,并获取它们的结果。 - Lock:来自
threading库,在多线程编程中,为了避免多个线程同时访问和修改同一资源导致的数据竞争,我们使用Lock来确保线程安全。
三、定义 spa1_thread 类
收起
1 | class spa1_thread: # 定义一个名为 spa1_thread 的类 |
__init__方法:这是类的构造函数,当我们创建1
spa1_thread
类的实例时会自动调用这个方法。
self.url:存储了一个基础的 URL 模板,其中{}是一个占位符,我们可以使用format方法将offset值插入到这个位置,实现翻页功能。self.headers:存储请求头信息,这是为了模拟浏览器请求,很多网站会检查请求头来防止爬虫,我们设置User-Agent和Referer来模拟一个正常的浏览器访问。self.all_moves:一个空列表,用于存储从 API 获取的电影信息。self.client:使用pymongo.MongoClient连接到本地的 MongoDB 服务器,host是服务器地址,port是端口号,这里是本地的默认地址和端口(localhost:27017)。self.db:选择spa1数据库和movies_thread集合,后续的数据将存储在这里。self.lock:创建一个锁对象,确保多线程环境下的数据操作安全。
四、获取数据的方法 get_data_info
1 | # 翻页获取请求并实现格式化数据 |
get_data_info方法:接收一个1
page
参数,根据页码获取电影信息。
print(f'现在正在获取第{page}页!'):在控制台打印正在获取的页码,方便我们跟踪程序的进度。url = self.url.format(page):使用format方法将page插入到self.url中,形成完整的请求 URL。response = requests.get(url, headers=self.headers):使用requests.get发送 GET 请求,同时带上请求头,这样服务器会认为我们是一个正常的浏览器请求。if response.status_code == 200:检查响应的状态码是否为 200,这表示请求成功。json_info = response.json():将响应的内容解析为 JSON 格式,方便我们提取数据。for move_data in json_info['results']:遍历results列表,这个列表包含了电影的信息。- 创建一个
move字典存储电影信息,使用get方法从move_data中提取每个字段的值。 with self.lock:使用Lock确保在多线程环境下,将move字典添加到self.all_moves列表的操作是原子性的,避免多个线程同时添加数据导致混乱。- 如果状态码不为 200,打印状态码异常信息;如果请求过程中发生异常,也会打印异常信息。
五、主函数 main
1 | # 开启多线程主函数 |
main方法:start_time = time.time():使用time.time()记录程序开始的时间。with ThreadPoolExecutor(max_workers=10) as t:创建一个最大线程数为 10 的线程池,这样可以同时运行多个任务,提高程序效率。t1 = [t.submit(self.get_data_info, page) for page in range(0, 100)]:使用列表推导式和submit方法将get_data_info方法提交到线程池,同时将 0 到 99 的页码作为参数传递给get_data_info方法。for future in as_completed(t1):使用as_completed迭代器,等待线程任务完成,future.result()可以获取任务的结果,如果任务发生异常会在此处抛出。self.save_info_mongo():调用save_info_mongo方法将数据存储到 MongoDB。end_time = time.time():记录程序结束的时间。print(f"数据爬取过程总时间:{end_time - start_time}秒"):计算并打印程序运行的总时间,这样可以评估程序性能。
六、保存数据到 MongoDB 的方法 save_info_mongo
1 | # 将数据保存进 MongoDB 数据库 |
save_info_mongo方法:if self.all_moves:检查self.all_moves列表是否有数据,如果有数据才进行存储操作。result = self.db.insert_many(self.all_moves):使用insert_many方法将self.all_moves中的所有数据一次性插入到 MongoDB 中,这样可以提高插入效率。print(f'已保存:{len(result.inserted_ids)}条数据'):打印插入的数据数量。self.db.create_index([('id', pymongo.ASCENDING)]):为id字段创建一个升序索引,方便后续查询和排序。- 如果
self.all_moves列表为空,打印相应信息;如果存储过程中发生pymongo异常,打印异常信息。
七、程序入口
1 | if __name__ == '__main__': # 程序入口 |
- 这部分代码确保程序只有在作为脚本直接运行时才会执行,而不是作为模块被导入时执行。首先创建
spa1_thread类的实例,然后调用main方法开始整个程序的执行。
八、总结
通过这个例子,我们学习了如何使用 Python 实现一个简单的数据爬取和存储程序。使用 requests 库可以方便地发送网络请求,使用 ThreadPoolExecutor 可以让我们的程序利用多线程并发执行,提高效率。同时,使用 pymongo 库将数据存储到 MongoDB 中,还使用了 Lock 确保多线程操作的数据安全。当然,在实际应用中,你可以根据自己的需求修改 headers 来适应不同的网站,调整线程数量,添加更多的异常处理,以及根据数据量调整存储策略等。希望这个案例可以帮助你更好地理解 Python 多线程编程和数据库操作,让你在数据爬取和存储的道路上迈出重要的一步。
九、注意事项
- 请确保 MongoDB 服务正在运行,并且 MongoDB 服务的地址和端口正确,否则会导致连接失败。
- 多线程操作时,虽然使用了
Lock确保数据添加的原子性,但也会带来性能开销,根据实际情况调整线程池大小和锁的使用。 - 对于
insert_many操作,如果数据量很大,可以考虑分批插入,以避免性能问题。 - 可以根据服务器性能和网络状况调整
max_workers的数量,以避免给服务器带来过大压力。 - 对于网络请求,可以添加更多的异常处理,例如设置
requests.get的超时时间,如requests.get(url, headers=self.headers, timeout=10)。 - 对于 MongoDB 的操作,根据实际情况可以添加更多的异常处理,例如处理插入重复数据的异常等。
十、完整代码附上
1 | import pymongo.errors |
—碎碎念:
准备打造一个python圈子,交流,学习,兼职搞钱!!!想进的可留言,可联系我邮箱看到会回复!
希望这篇博客对你学习 Python 多线程和数据存储有所帮助,如果你有任何问题或建议,欢迎在评论区留言哦 让我们一起学习,共同进步!
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 玛莎琪!
评论
