功能 对红色APP某博主的文章列表进行遍历,针对每一篇文章自动下载图集
并采集标题
,然后使用黑色软件的一键成片功能自动剪辑
刚下载的图集,把生成的短视频发布
出去。(可以扩展:下载生成的视频,发布到多个短视频平台)
开发环境
电脑 Win10 or 11
安卓手机 v7.0+
AScript
v3.0.13
某书
v8.26.0.dbe9ed0
某音
v29.0.0 (20240311)
下载地址:https://www.123pan.com/s/QZGSVv-mr6N3.html
安装和配置
某书和某音,如果是新安装
,注意把一些教程或申请权限弹出点掉,让APP进入一种日常
使用的状态
。
AScript,安装后给足权限,尤其是无障碍权限
,开发者中找到局域网IP
,在浏览器中打开这个地址,即为开发窗口,包含代码编辑器
、开发文档
、工具集合
。
需求分析 `循环`执行以下任务: 从手机桌面打开某书(默认打开后是首页) 从首页进入用户主页(文章列表页) `结果` = 从用户文章列表中找一篇合适的文章 如果`结果`是一篇文章: 点击文章,进入文章详情页面 `标题` = 采集标题任务 `图片个数` = 采集图片任务(采集图片们,并返回`图片个数`) 返回某书用户文章列表页面 打开某音(默认打开后是首页) 剪辑 发布 返回某音主页 返回手机桌面 另外的情况: 退出任务循环
写代码 采集标题
def collect_title (): """ 采集标题 :return: """ title_node = Selector(1 ).type ("ViewGroup" ).depth(13 ).brother(-0.1 ).child(1 ).find() return title_node.text
————————————————————
采集图片
def collect_images (): """ 收集图片,从图集详情第一张开始(等右上角的数字消失之后再开始) :return: 图片个数 """ recycler_node = Selector(1 ).type ("RecyclerView" ).depth(14 ).find() recycler_node_rect = recycler_node.rect jishu_node = recycler_node.parent(1 ).brother(-1 ) zongshu = len (jishu_node.child()) counter = 1 while True : image = screen.capture(recycler_node_rect.left, recycler_node_rect.top, recycler_node_rect.left + recycler_node_rect.width(), recycler_node_rect.top + recycler_node_rect.height()) screen.bitmap_to_file(R.sd(f'{counter} .jpg' ), image) recycler_node.swipe(-1 ) time.sleep(1 ) if counter + 1 > zongshu: break else : counter = counter + 1 return counter
————————————————————
返回某书用户文章列表页面
————————————————————
打开某音(默认打开后是首页)
————————————————————
剪辑
def images_to_movie (images_count ): """ 将图片合成视频 :param images_count: 图片数量 :return: """ jiahao_node = wait_for_node(Selector(1 ).type ("ImageView" ).desc("拍摄,按钮" )) action.Key.back() time.sleep(3 ) if jiahao_node: jiahao_node.parent(4 ).click() xiangce_btn = wait_for_node(Selector(1 ).type ("Button" ).desc("相册" )) xiangce_btn.click() time.sleep(3 ) gouxuanzu = Selector(1 ).type ("TextView" ).desc(", 未选中" ).find_all() for i in range (0 , images_count): gouxuan = gouxuanzu[i] gouxuan.parent(1 ).click() time.sleep(1 ) if i >= 11 : break next_btn = Selector(1 ).type ("TextView" ).text("下一步" ).clickable(True ).find() next_btn.click() time.sleep(4 ) yjcp_btn = Selector(1 ).type ("Button" ).desc("一键成片" ).find() yjcp_btn.click() time.sleep(6 ) xiaoguomen = Selector(1 ).type ("RecyclerView" ).depth(16 ).child().find_all() suijishu = random.randint(1 , len (xiaoguomen) - 1 ) xiaoguo = xiaoguomen[suijishu] xiaoguo.click() time.sleep(3 ) print (f'选择了索引为{suijishu} 的效果' ) baocun_btn = Selector(1 ).type ("TextView" ).text("保存" ).find() baocun_btn.click() time.sleep(3 ) xiayibu = Selector(1 ).text("下一步" ).desc("下一步" ).type ("TextView" ).find() xiayibu.parent(1 ).click() time.sleep(4 )
————————————————————
wait_for_node: 等待某个节点出现
def wait_for_node (selector, retry_times=10 , retry_interval=1 ): """ 等待某个节点出现 :param selector: 选择器 :param retry_times: 重试次数 :param retry_interval: 重试间隔 :return: 节点对象 或 None """ counter = 1 while counter <= retry_times: node = selector.find() if node: return node counter += 1 time.sleep(retry_interval) return None
————————————————————
发布
def post_video (title ): """ 发布视频 :return: """ miaoshukuang = wait_for_node(Selector(1 ).text("添加作品描述.." ).type ("EditText" )) miaoshukuang.input (title) fabu_btn = Selector(1 ).desc("发布" ).type ("FrameLayout" ).find() fabu_btn.click() time.sleep(5 )
————————————————————
返回某音主页
action.Key.back() time.sleep(2)
————————————————————
返回手机桌面
action.Key.home() time.sleep(2 )
————————————————————
带循环功能完整项目代码
import osimport randomimport refrom ascript.android.system import Rfrom ascript.android import actionfrom ascript.android import nodefrom ascript.android import screenfrom ascript.android import systemfrom ascript.android.node import Selectorimport timedef go_to_user_home (user_id ): """ 从首页进入用户主页 :param user_id: 用户id :return: """ search_btn = Selector(1 ).desc("搜索" ).type ("Button" ).depth(15 ).find() search_btn.click() time.sleep(2 ) search_input = Selector(1 ).text("搜索, " ).type ("EditText" ).find() search_input.input (user_id) time.sleep(1 ) right_search_button = Selector(1 ).text("搜索" ).type ("Button" ).find() right_search_button.click() time.sleep(3 ) user_btn = Selector(1 ).text("用户" ).type ("TextView" ).find() user_btn.parent(1 ).click() time.sleep(2 ) hong_shu_hao_2p = Selector(1 ).text("小红书号" ).type ("TextView" ).depth(13 ).parent(2 ).find() hong_shu_hao_2p.click() time.sleep(3 ) def select_a_right_article (): """ 选择一篇合适的文章 :return: 找到的文章 或 None """ previous_all_desc = '' while True : this_all_desc = '' articles = Selector(1 ).type ("RecyclerView" ).depth(18 ).child().find_all() for article in articles: desc = article.desc modified_desc = re.sub(r',[^,]*(,[^,]*)$' , '' , desc) this_all_desc = this_all_desc + modified_desc if modified_desc in handled_articles: continue else : handled_articles.append(modified_desc) return article if this_all_desc == previous_all_desc: return None else : previous_all_desc = this_all_desc recycler = Selector(1 ).type ("RecyclerView" ).depth(18 ).find() recycler.swipe(-1 ) time.sleep(2 ) def collect_title (): """ 采集标题 :return: """ title_node = Selector(1 ).type ("ViewGroup" ).depth(13 ).brother(-0.1 ).child(1 ).find() return title_node.text def collect_images (): """ 收集图片,从图集详情第一张开始(等右上角的数字消失之后再开始) :return: 图片个数 """ recycler_node = Selector(1 ).type ("RecyclerView" ).depth(14 ).find() recycler_node_rect = recycler_node.rect jishu_node = recycler_node.parent(1 ).brother(-1 ) zongshu = len (jishu_node.child()) counter = 1 while True : image = screen.capture(recycler_node_rect.left, recycler_node_rect.top, recycler_node_rect.left + recycler_node_rect.width(), recycler_node_rect.top + recycler_node_rect.height()) screen.bitmap_to_file(R.sd(f'{counter} .jpg' ), image) recycler_node.swipe(-1 ) time.sleep(1 ) if counter + 1 > zongshu: break else : counter = counter + 1 return counter def wait_for_node (selector, retry_times=10 , retry_interval=1 ): """ 等待某个节点出现 :param selector: 选择器 :param retry_times: 重试次数 :param retry_interval: 重试间隔 :return: 节点对象 或 None """ counter = 1 while counter <= retry_times: node = selector.find() if node: return node counter += 1 time.sleep(retry_interval) return None def images_to_movie (images_count ): """ 将图片合成视频 :param images_count: 图片数量 :return: """ jiahao_node = wait_for_node(Selector(1 ).type ("ImageView" ).desc("拍摄,按钮" )) action.Key.back() time.sleep(3 ) if jiahao_node: jiahao_node.parent(4 ).click() xiangce_btn = wait_for_node(Selector(1 ).type ("Button" ).desc("相册" )) xiangce_btn.click() time.sleep(3 ) gouxuanzu = Selector(1 ).type ("TextView" ).desc(", 未选中" ).find_all() for i in range (0 , images_count): gouxuan = gouxuanzu[i] gouxuan.parent(1 ).click() time.sleep(1 ) if i >= 11 : break next_btn = Selector(1 ).type ("TextView" ).text("下一步" ).clickable(True ).find() next_btn.click() time.sleep(4 ) yjcp_btn = Selector(1 ).type ("Button" ).desc("一键成片" ).find() yjcp_btn.click() time.sleep(6 ) xiaoguomen = Selector(1 ).type ("RecyclerView" ).depth(16 ).child().find_all() suijishu = random.randint(1 , len (xiaoguomen) - 1 ) xiaoguo = xiaoguomen[suijishu] xiaoguo.click() time.sleep(3 ) print (f'选择了索引为{suijishu} 的效果' ) baocun_btn = Selector(1 ).type ("TextView" ).text("保存" ).find() baocun_btn.click() time.sleep(3 ) xiayibu = Selector(1 ).text("下一步" ).desc("下一步" ).type ("TextView" ).find() xiayibu.parent(1 ).click() time.sleep(4 ) def post_video (title ): """ 发布视频 :return: """ miaoshukuang = wait_for_node(Selector(1 ).text("添加作品描述.." ).type ("EditText" )) miaoshukuang.input (title) fabu_btn = Selector(1 ).desc("发布" ).type ("FrameLayout" ).find() fabu_btn.click() time.sleep(5 ) handled_articles = [] while True : system.open ('小红书' ) time.sleep(5 ) go_to_user_home('3915813063' ) article = select_a_right_article() if article: article.click() time.sleep(6 ) title = collect_title() images_count = collect_images() action.Key.back() time.sleep(1 ) action.Key.back() time.sleep(1 ) action.Key.back() time.sleep(1 ) action.Key.back() time.sleep(2 ) system.open ('抖音' ) images_to_movie(images_count) post_video(title) action.Key.back() time.sleep(2 ) action.Key.home() time.sleep(2 ) else : break
心得体会 脚本的稳定性是需要打磨
的,很多特殊情况在你开发脚本流程的时候你是遇不到的,但实际应用中特殊情况随时可能出现,一个稳定的脚本是需要反复测试
反复打磨
才能投入使用的。