Appium爬取wx朋友圈
- 用Appium实现微信朋友圈动态信息抓取,主要包括好友昵称、正文、发布时间;其中正文这一部分我并没有爬取下来;
1、准备工作
- PC端安装好Appium,Android开发环境和Python版本的Appium API,如何安装Appium: https://blog.csdn.net/weixin_43411585/article/details/89277056
- 以及PyMongo库,MongoDB,如何安装MongoDB: https://blog.csdn.net/weixin_43411585/article/details/88833680
- Appium启动App的方式有两种:一种是用Appium内置的驱动器打开App,另一种是用Python程序来实现此操作
- 将Android手机通过数据线和运行Appium的PC相连,同时打开USB调试功能,确保PC连接到手机
- Appium如何使用
- 完整代码见链接,其中一些参数查找可能因为机型不同而不同,具体可以先通过Appium内置驱动模拟操作一遍,看每个元素的标签id是什么,而进行修改;主要是发现按钮,朋友圈按钮,昵称,时间这四个元素,主要代码
2、爬取思路
- (1)初始化配置,如驱动配置,MongoDB连接配置等
class Moments(): def __init__(self): """ 初始化 """ # 驱动配置 self.desired_caps = { 'platformName': PLATFORM, 'deviceName': DEVICE_NAME, 'appPackage': APP_PACKAGE, 'appActivity': APP_ACTIVITY, 'noReset': "True" } self.driver = webdriver.Remote(DRIVER_SERVER, self.desired_caps) self.client = MongoClient(MONGO_URL) self.db = self.client[MONGO_DB] self.collection = self.db[MONGO_COLLECTION] # 处理器 self.processor = Processor()
- (2)模拟登陆,由于我在Appium驱动配置上加了参数:‘noReset’: “True”;数据不重置,所以模拟登陆这一步可以直接跳过,只要微信是登陆的,代码运行的时候就是直接在已登录状态中;
- (3)直接选择发现选项卡→朋友圈进入;
def enter(self): """ 进入朋友圈 :return: """ # 选项卡 time.sleep(15) tab = self.driver.find_elements(By.XPATH, '//*[@resource-id="com.tencent.mm:id/r4"]')[2] tab.click() time.sleep(15) # 朋友圈 moments = self.driver.find_element(By.ID, 'android:id/title') moments.click() print("已进入朋友圈")
- (4)抓取动态,由于朋友圈需要拖动,才能看到更多内容,所以,这里需要模拟一个拖动的操作,进行循环;driver.swipe(300,1000,300,300);首先获取点前显示的朋友圈的每条状态对应的区块元素,遍历每个区块元素,再获取内部显示的用户名,和发布时间;
def crawl(self): """ 爬取 :return: """ for i in range(15): time.sleep(2) # 上滑 self.driver.swipe(300, 1000, 300, 300) # 当前页面显示的所有状态 items = self.driver.find_elements(By.XPATH, '//*[@resource-id="com.tencent.mm:id/ej_"]') # 遍历每条状态 for item in items: try: # 昵称 nickname = item.find_element(By.XPATH, '//*[@resource-id="com.tencent.mm:id/b5o"]').get_attribute('text') # # 正文 # content = item.find_element(By.XPATH, '//*[@resource-id="com.tencent.mm:id/kt"]').get_attribute('text') # # 日期 date = item.find_element(By.XPATH, '//*[@resource-id="com.tencent.mm:id/eec"]').get_attribute('text') # 处理日期 date = self.processor.date(date) print(nickname, date) data = { 'nickname': nickname, # 'content': content, 'date': date, }
- (5)对于时间显示几小时前或者几分钟前的,我们用正则匹配的方法来提取时间中的具体数值,再利用时间转换函数实现时间的转换;例如5分钟前,这个方法先将5提取出来,用当前时间戳减去300即可得到发布的时间戳,然后再转换为标准时间即可。
class Processor(): def date(self, datetime): """ 处理时间 :param datetime: 原始时间 :return: 处理后时间 """ if re.match('\d+分钟前', datetime): minute = re.match('(\d+)', datetime).group(1) datetime = time.strftime('%Y-%m-%d', time.localtime(time.time() - float(minute) * 60)) if re.match('\d+小时前', datetime): hour = re.match('(\d+)', datetime).group(1) datetime = time.strftime('%Y-%m-%d', time.localtime(time.time() - float(hour) * 60 * 60)) if re.match('昨天', datetime): datetime = time.strftime('%Y-%m-%d', time.localtime(time.time() - 24 * 60 * 60)) if re.match('\d+天前', datetime): day = re.match('(\d+)', datetime).group(1) datetime = time.strftime('%Y-%m-%d', time.localtime(time.time()) - float(day) * 24 * 60 * 60) return datetime
- (6)最后调用MongoDB的API来实现爬取结果的存储。为了去除重复,这里调用了update()方法,这个操作的关键点事第三个参数True,此参数设置为True,可以实现存在即更新。不存在则插入的操作实现如下所示:
self.collection.update_one({'nickname': nickname}, {'$set': data}, True)