乱码三千 – 分享实用IT技术

乱码三千 – 码出一个新世界


  • 首页

  • 归档

  • 搜索

Android中关于设置屏幕是否休眠的几种方法介绍

发表于 2021-12-21

前言

我们在做视频播放的放得时候 , 肯定会有这样的一个需求, 那就是视频播放时禁止锁屏息屏, 视频暂停或者退出时恢复锁屏息屏

那么, 有以下几种方法供大家参考

方法一:持有WakeLock

添加休眠锁,休眠锁必须成对出现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private WakeLock mWakeLock = null;

private void acquireWakeLock() {
if(mWakeLock == null) {

PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);

mWakeLock = pm.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK |
PowerManager.ACQUIRE_CAUSES_WAKEUP,
this.getClass().getCanonicalName());

mWakeLock.acquire();
}

}
@Override
protected void onResume() {
super.onResume();
acquireWakeLock();
}

@Override
protected void onPause() {
super.onPause();
releaseWakeLock();
}

private void acquireWakeLock(){
if(mWakeLock != null) {
mWakeLock.acquire();
}
}
private void releaseWakeLock() {
if(mWakeLock != null) {
mWakeLock.release();
///mWakeLock = null;
}
}

在onResume以及onPause执行相应操作

AndroidManifest.xml 文件中添加权限:

1
2
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.DEVICE_POWER" />

WakeLock获取时相关的flag如下所示:

  • PARTIAL_WAKE_LOCK :保持CPU 运转,屏幕和键盘灯有可能是关闭的。
  • SCREEN_DIM_WAKE_LOCK :保持CPU 运转,允许保持屏幕显示但有可能是灰的,允许关闭键盘灯
  • SCREEN_BRIGHT_WAKE_LOCK :保持CPU 运转,允许保持屏幕高亮显示,允许关闭键盘灯
  • FULL_WAKE_LOCK :保持CPU 运转,保持屏幕高亮显示,键盘灯也保持亮度

PS:现在官方已经不推荐使用这种方式保持亮屏了,推荐改为以下两种方式

方法二:在Window设置flag

在Activity布局中设置Windows属性进行控制,但是一定要在加载布局之前执行,此种方法的局限性是只有在Activity类中有效,Activity退出时失效。

1
2
3
4
5
6
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, 
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.***);

//或者设置下面的也一样
//getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

这种方式不需要申请权限,也是官方推荐的做法

这个方法的好处是,与wakelocks不同 ,它不需要具体的权限,并且在换不同应用程序的操作中,系统会管理,不必担心没有释放的未使用的资源。

你并不需要清除FLAG_KEEP_SCREEN_ON标志,除非你不再需要在屏幕上停留在你运行应用程序。当应用程序进入后台或返回到前台发生,窗口管理器负责保障正常的事件处理,但是如果你明确的想要清除这个标致,从而允许屏幕熄灭,可以使用 clearFlags() 方法

如下:

1
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)

就可以控制屏幕熄灭了

*方法三:在界面布局xml中顶层添加属性

在View布局中添加属性,此种方法的好处是不一定非要在Activity界面运行时屏幕常亮才有效,View启动后可以自主设置。

View的xml布局中加入:

在XML文件中,使用android:keepScreenOn属性

1
2
3
4
5
6
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true">
...
</relativelayout>

这个设置 android:keepScreenOn=“true” 等同于FLAG_KEEP_SCREEN_ON。

代码中执行是否保持屏幕常亮:

1
2
View.setKeepScreenOn(true);
View.setKeepScreenOn(false);

转载自:https://blog.csdn.net/CLinuxF/article/details/103273411

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

关于机场订阅链接转换

发表于 2021-12-16

前言

一直在用免费的大爬梯, 虽然不是太稳定, 但是也够用了 , 平常外网需求并不是太多

给大家推荐一个机场订阅链接在线转换工具:

点击进入

image-20211216113509711

该项目已经开源, 源码地址如下:

GitHub

哪里找大爬梯

我个人平常的大爬梯需求基本上都是在Github上找的, 比如 V2Free

其他的大家可以自行搜索

另外以下这个网站每天也会更新ssr和TG节点:

  • SSR

  • 免费TG代理

目前在用的是V2free的免费服务, 每天签到可以获取免费流量, 配合ClashX使用, 速度还不错哦

如下图:

image-20211216115040948

具体参见: 点击进入

image-20211216114733195

具体参见: 点击进入, 备用地址

附加

  • Telegram频道在线搜索

大爬梯试用

如果你不想花钱, 只是紧急临时用一下, 可以考虑一些提供试用的大爬梯软件, 比如:

  • 西柚加速器: 官网链接

    一个手机号可以试用一天, 如果你有多个手机号, 那么 嘿嘿

  • auVPN: 官网链接

    0.9美元试用7天, 支持支付宝付款

  • v2free: 官网链接 GitHub

    3元试用三天

注意事项

如果打算长期使用某一款大爬梯, 切记不要选择那种用手机号注册的, 尽量选择用邮箱注册, 邮箱也最好使用国外的, 至于为什么, 大家都懂的哈哈

本文为作者原创转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

2021年终总结 这要从我抽中了小爱音响开始说起

发表于 2021-12-14

最近参加了掘金的年终总结活动, 临时起意写了一篇文章, 点击进入掘金, 为了礼品我也是够拼了 哈哈 以下是原稿:

站立和倾斜在树上的女人

「时光不负,创作不停,本文正在参加2021年终总结征文大赛」

所谓无图不丈夫 XDM喜欢否?

前言

今天办公室异常闷热 热得我一看屏幕就眼睛发肿, 导致无法正常工作

于是乎 原本没打算参加年终总结活动的我写起了总结

唯一的动力就是掘金的大礼 哈哈

说到大礼 昨天不小心抽中了一个小爱音响 还在活动群炫耀了一番:

image-20211214100831346

image-20211214100925150

说到抽奖 作为一个在年会经常抽中一等奖的我 在这给大家分享一下个人的心得:

  • 首先 你要有一个抽奖资格 …

  • 然后 在抽奖的这段时间里 内心一定要保持非常开心的状态 让自己兴奋起来 男同胞们如果不够兴奋可以多看几遍上面那张大图, 也就是让抽奖机感受到你的激情 让对方明白你非常想要 哈哈

  • 这听起来似乎有点玄 不过没关系 不用太在意这些细节 你要想象成这奖品已经到了手

  • 一切准备好后 开始抽奖 那么中奖概率能大幅提升一个档次 咳咳

如果抱着无所谓的态度 那么抽中的概率会大大降低 , 所谓心心念念 必有回响嘛

以上只是个人经验瞎扯 无科学根据 仅供参考哈哈

下面附上我16年公司年会抽中的一个ipad mini:

image-20211214100209725

目前将其当做第二屏使用, 提升生产力的同时, 还能缓解颈椎不适, 嘿嘿

今年收获

今年收获很大, 话不多说, 直接看下面:

  • SKG颈椎按摩器时尚语音款 一台
  • 瑞视达T1手机投影仪 一台
  • 小霸王D99增强版游戏机 一台
  • 小熊咖啡机全自动滴漏式0.7L 一个
  • 多元兼容黑色帆布双肩包 一个
  • 未来星乐园”定制积木 一座
  • 字节跳动智能感温保温杯 一只
  • 掘金 T 恤 一件
  • 掘金棒球帽 两顶
  • 掘金搪瓷杯 两只
  • 字节跳动空调毯 一床
  • 小册优惠码 两个
  • 掘金Bug 52个

image-20211214103132112

image-20211214103224187

明年Flag

明年我打算把Q400游戏机拿下 ​​

未来五年

争取把任天堂 NS 拿下

我已经做好了打持久战的准备了 哈哈哈

最后

再次感谢掘金, 让我收获了这么多的礼品

另外

近期 我的第三张个人原创音乐专辑已经全网上线了, 欢迎大家前来捧场, 以下是网易云试听链接:

茶余饭后

​

本文为作者原创转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

一个简单的在线制作 lrc 文件的小工具

发表于 2021-12-11

前言

最近音乐专辑上线, 需要给歌曲制作lrc滚动歌词, Mac平台这方好使的软件不多, 于是尝试找个能在线通过网页制作歌词的网站, 最后找到了这个:

网站地址: https://judes.me/lrc_editor/

用了一下 感觉还不错, 而且项目是开源的, 源码地址如下:

GitHub链接

音乐专辑

我的第三张个人原创音乐专辑《茶余饭后》大部分歌曲歌词都是用这个工具制作的, 以下是网易试听链接:

专辑已经全网上架, 欢迎大家来捧场哦~

​

本文为作者原创转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Web自动化框架selenium的介绍与使用

发表于 2021-12-02

介绍

selenium 是一个 web 的自动化测试工具,可以模拟人工Web浏览,, 适合用于自动化处理和爬虫任务, 该框架支持C、 java、ruby、python等多种语言, 支持三大操作系统和各种常见浏览器

官方文档:

  • https://selenium-python.readthedocs.io/index.html
  • https://seleniumhq.github.io/selenium/docs/api/py/api.html

接下来以python为例, 给大家介绍selenium 的具体使用

selenium使用前准备

  1. 框架安装

    1
    pip install selenium
  2. web驱动安装

    推荐下载使用谷歌或火狐浏览器, 然后根据浏览器版本安装与之对应版本的驱动, 这里我使用的是谷歌浏览器

    打开浏览器设置, 查看浏览器版本:

    在这里插入图片描述

    然后下载驱动:

    ChromeDriver下载地址

    在这里插入图片描述

    谷歌123之后的版本驱动下载地址

    image-20240417115034597

    将下载的驱动解压到以下目录:

    1
    2
    Win:复制webdriver到Python安装目录下
    Mac:复制webdriver到/usr/local/bin目录下

    如果是火狐浏览器同理,以下是两大类浏览器驱动下载备用链接:

    Chrome ( chromedriver ) Firefox ( geckodriver )
    官方下载 官方下载
    淘宝镜像 淘宝镜像
    备用下载 备用下载

    接下来 我们开始引入框架并使用

  3. 框架引入

    1
    from selenium import webdriver

开始使用

  1. 自动打开浏览器访问某个网页

    1
    2
    3
    4
    from selenium import webdriver

    browser = webdriver.Chrome()
    browser.get('http://www.baidu.com/')
  2. 获取网页的源码

    1
    2
    3
    4
    5
    from selenium import webdriver

    browser = webdriver.Chrome()
    browser.get('http://www.baidu.com/')
    print(browser.page_source)
  3. 不打开浏览器获取网页源码

    很多情况下我们只需要让程序在后台静默执行就行, 不需要频繁开启浏览器, 那么我们需要对驱动进行相关设置, 如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    from selenium import webdriver
    chrome_options = webdriver.ChromeOptions()
    # 使用headless无界面浏览器模式
    chrome_options.add_argument('--headless') #增加无界面选项
    chrome_options.add_argument('--disable-gpu') #如果不加这个选项,有时定位会出现问题

    browser = webdriver.Chrome(chrome_options=chrome_options)
    browser.get('http://www.baidu.com/')
    print(browser.page_source)
  4. 以指定配置启动浏览器

    常用浏览器有常用的一些设置, 如果我们想在自动化的过程中加载进来可以使用add_argument方法, 如下:

    1
    2
    3
    4
    5
    #coding=utf-8
    from selenium import webdriver
    option = webdriver.ChromeOptions()
    option.add_argument('--user-data-dir=C:\Users\Administrator\AppData\Local\Google\Chrome\User Data') #指定数据目录
    driver=webdriver.Chrome(chrome_options=option)
  5. 指定驱动的目录

    除了将下载的驱动存放到系统指定目录使用之外, 我们还可以直接指定驱动的存放路径, 方便我们临时测试多个不同的驱动效果, 配置方法如下:

    1
    2
    3
    driver_path = r'D:\ProgramApp\chromedriver\chromedriver.exe'
    # 初始化一个driver,并且指定chromedriver的路径
    driver = webdriver.Chrome(executable_path=driver_path)
  6. webdriver常用api汇总

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    browser = webdriver.Chrome()

    browser.get_cookies() #获取所有的cookies
    browser.page_source #获取网页源码
    browser.delete_all_cookies() #删除所有cookies
    browser.delete_cookie(key) #删除某个cookies
    browser.implicitly_wait(10) #设置超时时间
    browser.execute_script("window.open('https://www.douban.com/')") #打开一个新页面
    browser.title #获取网页标题
    browser.current_url #获取当前网页url
    browser.capabilities['version'] #获取浏览器版本号
    browser.maximize_window() #浏览器最大化
    browser.minimize_window() #浏览器最小化
    browser.set_window_size(480, 800) #设置浏览器宽高
    browser.forword() # 页面前进
    browser.back() #页面后退
    browser.close() #关闭当前页面
    browser.quit() #退出整个浏览器

    注意: 如果访问一些需要有cookie验证的页面,我们可以先访问主页,然后再访问详情页,webdriver会自动携带cookie

  7. ChromeOptions常用函数汇总

    1
    2
    #设置代理ip
    options.add_argument("--proxy-server=http://110.73.2.248:8123")
  1. 网页元素定位查找

    • 通过元素ID进行定位

      1
      browser.find_element_by_id()
    • 通过标签名的方式进行定位

      1
      browser.find_element_by_tag_name("input")
    • 通过class的方式进行定位

      1
      browser.find_element_by_class_name("xxx")
    • 通过css方式定位

      1
      browser.find_element_by_css_selector("xxx")
  1. 事件模拟

    主要分为键盘事件和鼠标事件

    1. 键盘事件

      通过 send_keys()调用键盘按键, 比如:

      1
      2
      3
      4
      from selenium.webdriver.common.keys import Keys

      send_keys(Keys.TAB) # TAB
      send_keys(Keys.ENTER) # 回车

      参考代码如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      #coding=utf-8 
      from selenium import webdriver
      from selenium.webdriver.common.keys import Keys #需要引入 keys 包
      import os,time

      driver = webdriver.Firefox()
      driver.get("http://passport.kuaibo.com/login/?referrer=http%3A%2F%2Fwebcloud .kuaibo.com%2F")

      time.sleep(3)
      driver.maximize_window() # 浏览器全屏显示

      driver.find_element_by_id("user_name").clear()
      driver.find_element_by_id("user_name").send_keys("fnngj")

      #tab 的定位相相于清除了密码框的默认提示信息,等同上面的 clear()
      driver.find_element_by_id("user_name").send_keys(Keys.TAB)
      time.sleep(3)
      driver.find_element_by_id("user_pwd").send_keys("123456")

      #通过定位密码框,enter(回车)来代替登陆按钮
      driver.find_element_by_id("user_pwd").send_keys(Keys.ENTER)

      #也可定位登陆按钮,通过 enter(回车)代替 click()
      driver.find_element_by_id("login").send_keys(Keys.ENTER)
      time.sleep(3)

      driver.quit()

      键盘组合键的用法:

      1
      2
      #ctrl+a 全选输入框内容 
      driver.find_element_by_id("kw").send_keys(Keys.CONTROL,'a')
      1
      2
      #ctrl+x 剪切输入框内容 
      driver.find_element_by_id("kw").send_keys(Keys.CONTROL,'x')
  1. 鼠标事件

    鼠标事件一般包括鼠标右键、双击、拖动、移动鼠标到某个元素上等等。 需要引入ActionChains类

    引入方法:

    1
    from selenium.webdriver.common.action_chains import ActionChains

    ActionChains 常用方法:

    1
    2
    3
    4
    5
    6

    perform() 执行所有ActionChains 中存储的行为;
    context_click() 右击;
    double_click() 双击;
    drag_and_drop() 拖动;
    move_to_element() 鼠标悬停。

    鼠标双击示例:

    1
    2
    3
    4
    #定位到要双击的元素
    qqq =driver.find_element_by_xpath("xxx")
    #对定位到的元素执行鼠标双击操作
    ActionChains(driver).double_click(qqq).perform()

    鼠标拖放示例:

    1
    2
    3
    4
    5
    6
    #定位元素的原位置 
    element = driver.find_element_by_name("source")
    #定位元素要移动到的目标位置
    target = driver.find_element_by_name("target")
    #执行元素的移动操作
    ActionChains(driver).drag_and_drop(element, target).perform()
  1. 特殊定位

    1. iframe定位

      1
      2
      #先找到到 ifrome1(id = f1)
      browser.switch_to_frame("f1")
    2. 内嵌窗口定位:

      1
      browser.switch_to_window("f1")

附加内容

  1. Python Webdriver Exception速查表

    webdriver在使用过程中可能会出现各种异常,我们需要了解该异常并知道如何进行异常处理。

    异常 描述
    WebDriverException 所有webdriver异常的基类,当有异常且不属于下列异常时抛出
    InvalidSwitchToTargetException 下面两个异常的父类,当要switch的目标不存在时抛出
    NoSuchFrameException 当你想要用switch_to.frame()切入某个不存在的frame时抛出
    NoSuchWindowException 当你想要用switch_to.window()切入某个不存在的window时抛出
    NoSuchElementException 元素不存在,一般由find_element与find_elements抛出
    NoSuchAttributeException 一般你获取不存在的元素属性时抛出,要注意有些属性在不同浏览器里是有不同的属性名的
    StaleElementReferenceException 指定的元素过时了,不在现在的DOM树里了,可能是被删除了或者是页面或iframe刷新了
    UnexpectedAlertPresentException 出现了意料之外的alert,阻碍了指令的执行时抛出
    NoAlertPresentException 你想要获取alert,但实际没有alert出现时抛出
    InvalidElementStateException 下面两个异常的父类,当元素状态不能进行想要的操作时抛出
    ElementNotVisibleException 元素存在,但是不可见,不可以与之交互
    ElementNotSelectableException 当你想要选择一个不可被选择的元素时抛出
    InvalidSelectorException 一般当你xpath语法错误的时候抛出这个错
    InvalidCookieDomainException 当你想要在非当前url的域里添加cookie时抛出
    UnableToSetCookieException 当driver无法添加一个cookie时抛出
    TimeoutException 当一个指令在足够的时间内没有完成时抛出
    MoveTargetOutOfBoundsException actions的move操作时抛出,将目标移动出了window之外
    UnexpectedTagNameException 获取到的元素标签不符合要求时抛出,比如实例化Select,你传入了非select标签的元素时
    ImeNotAvailableException 输入法不支持的时候抛出,这里两个异常不常见,ime引擎据说是仅用于linux下对中文/日文支持的时候
    ImeActivationFailedException 激活输入法失败时抛出
    ErrorInResponseException 不常见,server端出错时可能会抛
    RemoteDriverServerException 不常见,好像是在某些情况下驱动启动浏览器失败的时候会报这个错

  2. Xpath&Css定位方法速查表

    描述 Xpath Css
    直接子元素 //div/a div > a
    子元素或后代元素 //div//a div a
    以id定位 //div[@id=’idValue’]//a div#idValue a
    以class定位 //div[@class=’classValue’]//a div.classValue a
    同级弟弟元素 //ul/li[@class=’first’]/following- ul>li.first + li
    属性 //form/input[@name=’username’] form input[name=’username’]
    多个属性 //input[@name=’continue’ and input[name=’continue’][type=’button
    第4个子元素 //ul[@id=’list’]/li[4] ul#list li:nth-child(4)
    第1个子元素 //ul[@id=’list’]/li[1] ul#list li:first-child
    最后1个子元素 //ul[@id=’list’]/li[last()] ul#list li:last-child
    属性包含某字段 //div[contains(@title,’Title’)] div[title*=”Title”]
    属性以某字段开头 //input[starts-with(@name,’user’)] input[name^=”user”]
    属性以某字段结尾 //input[ends-with(@name,’name’)] input[name$=”name”]
    text中包含某字段 //div[contains(text(), ‘text’)] 无法定位
    元素有某属性 //div[@title] div[title]
    父节点 //div/.. 无法定位
    同级哥哥节点 //li/preceding-sibling::div[1] 无法定位

​

​

​

本文为作者原创转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Python爬虫框架之PyQuery的使用

发表于 2021-12-02

前言

Python之所以很适合写爬虫, 其中一个原因是拥有丰富的解析器Lib

以HTML解析为例就有XPATH,Beautiful Soup和PyQuery等等

想用哪个用哪个

那么今天我们就来介绍其中PyQuery这个解析器的用法, PyQuery的语法和jquery大同小异, 如果你熟悉jquery的使用, 那么这款解析器绝对适合你

组件安装

1
pip install pyquery

组件引入

1
from pyquery import PyQuery as pq

三个小示例

假设我们有这么一个HTML文本:

1
2
3
4
5
6
7
8
<div id="wrap">
<ul class="s_from">
我是测试文本
<link href="http://aaa.com">aaa</link>
<link href="http://bbb.com">bbb</link>
<link href="http://ccc.com">ccc</link>
</ul>
</div>

如果我们要获取div标签下的所有内容(包含div标签), 那么我们可以这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pyquery import PyQuery as pq
html = '''
<div id="wrap">
<ul class="s_from">
我是测试文本
<link href="http://aaa.com">aaa</link>
<link href="http://bbb.com">bbb</link>
<link href="http://ccc.com">ccc</link>
</ul>
</div>
'''
doc = pq(html)
result=doc("#wrap")
print(result)

得到的结果为:

1
2
3
4
5
6
7
8
<div id="wrap">
<ul class="s_from">
我是测试文本
<link href="http://aaa.com">aaa</link>
<link href="http://bbb.com">bbb</link>
<link href="http://ccc.com">ccc</link>
</ul>
</div>

如果我们要获取div标签下的所有元素(不包含div标签),, 那么我们可以使用children()函数, 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pyquery import PyQuery as pq
html = '''
<div id="wrap">
<ul class="s_from">
我是测试文本
<link href="http://aaa.com">aaa</link>
<link href="http://bbb.com">bbb</link>
<link href="http://ccc.com">ccc</link>
</ul>
</div>
'''
doc = pq(html)
result=doc("#wrap").children()
print(result)

那么得到的结果为:

1
2
3
4
5
6
<ul class="s_from">
我是测试文本
<link href="http://aaa.com">aaa</link>
<link href="http://bbb.com">bbb</link>
<link href="http://ccc.com">ccc</link>
</ul>

如果我们要获取ul标签下的所有内容, 我们可以这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pyquery import PyQuery as pq
html = '''
<div id="wrap">
<ul class="s_from">
我是测试文本
<link href="http://aaa.com">aaa</link>
<link href="http://bbb.com">bbb</link>
<link href="http://ccc.com">ccc</link>
</ul>
</div>
'''
doc = pq(html)
result=doc("ul").html()
print(result)

那么得到的结果为:

1
2
3
4
我是测试文本
<link href="http://aaa.com">aaa</link>
<link href="http://bbb.com">bbb</link>
<link href="http://ccc.com">ccc</link>

通过上面三个小例子示范我们大致感受了一下PyQuery的使用, 但是依然无法深入了解, 接下来我们对其用法进行总结

使用总结

  1. 通过id获取html使用#

    1
    pq(html)("#xxx")
  2. 通过class获取html使用’.`

    1
    pq(html)(".xxx")
  3. 通过标签名获取html直接使用标签

    1
    pq(html)("div")
  4. 查找父元素使用parent()

    1
    pq(html)("#xxx").parent()
  5. 获取目标位置下包裹的所有的元素

    1
    pq(html)("#xxx").children()
  6. 获取目标位置下包裹的所有的html内容

    1
    pq(html)("#xxx").html()
  7. 获取目标位置下包裹的所有的文本内容

    1
    pq(html)("#xxx").text()

    注意: 我们要和html()与children()进行区分, 我们用一个例子来解释:

    如果使用html():

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # coding:utf-8

    from pyquery import PyQuery as pq
    html = '''
    <div id="wrap">
    <ul class="s_from">
    我是测试文本
    <link href="http://aaa.com">aaa</link>
    <link href="http://bbb.com">bbb</link>
    <link href="http://ccc.com">ccc</link>
    </ul>
    </div>
    <link href="http://ddd.com">ccc</link>
    </ul>
    '''
    doc = pq(html)
    result=doc("#wrap").html()
    print(result)

    得到的结果为:

    1
    2
    3
    4
    我是测试文本
    <link href="http://aaa.com"/><p>aaa</p>
    <link href="http://bbb.com"/>bbb
    <link href="http://ccc.com"/>ccc

    如果使用children():

    1
    2
    3
    doc = pq(html)
    result=doc(".s_from").children()
    print(result)

    得到结果为:

    1
    2
    3
    <link href="http://aaa.com"/><p>aaa</p>
    <link href="http://bbb.com"/>bbb
    <link href="http://ccc.com"/>ccc

    如果使用text():

    1
    2
    3
    doc = pq(html)
    result=doc(".s_from").text()
    print(result)

    得到结果为:

    1
    2
    3
    4
    我是测试文本
    aaa
    bbb
    ccc

    一目了然

  8. 查找兄弟元素使用siblings()

    1
    pq(html)("#xxx").siblings()
  9. 将所有获取到的元素组成数组items()

    1
    pq(html)("#xxx").items()

    比如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # coding:utf-8

    from pyquery import PyQuery as pq
    html = '''
    <div id="wrap">
    <ul class="s_from">
    我是测试文本
    <link href="http://aaa.com">aaa</link>
    <link href="http://bbb.com">bbb</link>
    <link href="http://ccc.com">ccc</link>
    </ul>
    </div>
    <link href="http://ddd.com">ccc</link>
    </ul>
    '''
    doc = pq(html)
    result=doc("link")
    print(result)

    那么得到的结果为:

    1
    2
    3
    4
    <link href="http://aaa.com"/>aaa
    <link href="http://bbb.com"/>bbb
    <link href="http://ccc.com"/>ccc
    <link href="http://ddd.com"/>ccc

    如果我们想对其进行遍历获取其中某一个元素, 那么可以这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # coding:utf-8

    from pyquery import PyQuery as pq
    html = '''
    <div id="wrap">
    <ul class="s_from">
    我是测试文本
    <link href="http://aaa.com">aaa</link>
    <link href="http://bbb.com">bbb</link>
    <link href="http://ccc.com">ccc</link>
    </ul>
    </div>
    <link href="http://ddd.com">ccc</link>
    </ul>
    '''
    doc = pq(html)
    result=doc("link").items()
    for i in result:
    print(i)
  10. 获取标签属性信息使用attr()

    还是上面的例子, 如果我们要获取link标签中href的值, 那么可以这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # coding:utf-8

    from pyquery import PyQuery as pq
    html = '''
    <div id="wrap">
    <ul class="s_from">
    我是测试文本
    <link href="http://aaa.com">aaa</link>
    <link href="http://bbb.com">bbb</link>
    <link href="http://ccc.com">ccc</link>
    </ul>
    </div>
    <link href="http://ddd.com">ccc</link>
    </ul>
    '''
    doc = pq(html)
    result=doc("link").items()
    for i in result:
    print(i.attr('href'))
    #或者
    print(i.attr.href)
    # 上面两种获取href的方法任选其一
  11. 精确查找使用空格

    啥意思呢, 我们还是用一个例子来说明:

    如果我们要利用层级关系精确查找, 那么可以这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # coding:utf-8

    from pyquery import PyQuery as pq
    html = '''
    <div id="wrap">
    <ul class="s_from">
    我是测试文本
    <link id="first" "href="http://aaa.com">111<p>aaa</p></link>
    <link href="http://bbb.com">bbb</link>
    <link href="http://ccc.com">ccc</link>
    </ul>
    </div>
    <link href="http://ddd.com">ccc</link>
    </ul>
    '''
    doc = pq(html)
    result=doc("#wrap .s_from #first")
    print(result)

    层级之间使用空格进行分隔, 得到的结果为:

    1
    <link id="first"/>111

    我们惊奇地发现, 查找到的内容有缺失,居然没有打印<p>aaa</p>, 如果我们将link标签改为a标签:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # coding:utf-8

    from pyquery import PyQuery as pq
    html = '''
    <div id="wrap">
    <ul class="s_from">
    我是测试文本
    <a id="first" "href="http://aaa.com">111<p>aaa</p></a>
    <link href="http://bbb.com">bbb</link>
    <link href="http://ccc.com">ccc</link>
    </ul>
    </div>
    <link href="http://ddd.com">ccc</link>
    </ul>
    '''
    doc = pq(html)
    result=doc("#wrap .s_from #first")
    print(result)

    则结果为:

    1
    <a id="first">111<p>aaa</p></a>

    你可能会说之前写的语法有问题 link标签怎么能又嵌套其他标签呢, 事实上, 你也难保会碰上诸如此类的情况, 毕竟前端鱼龙混杂, 当我们获取不到值的时候, 需要特殊注意一下

  1. 根据标签属性精确查找

    比如 我们要查找itemprop属性值为keywords的meta标签, 可以这样写:

    1
    2
    doc = pq(html)
    doc('meta[itemprop="keywords"]')

    如果需要嵌套查找 比如查找itemprop属性值为author的div标签下的itemprop属性值为name的meta标签 可以这样写:

    1
    2
    doc = pq(html)
    d('div[itemprop="author"]')('meta[itemprop="name"]')

    更多层级的嵌套也是类的写法

  2. 给元素添加class

    1
    pq(html)("#xxx").addClass('active')

    ​ 如果class已经存在, 则不重复添加

  3. 移除元素的class

    1
    pq(html)("#xxx").removeClass('active')
  4. 给元素添加css样式

    1
    pq(html)("#xxx").css('font-size','14px')
  5. 移除某个标签

    1
    pq(html)("#xxx").remove('ul')
  6. 伪类选择器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    from pyquery import PyQuery as pq
    html = '''
    <div href="wrap">
    hello nihao
    <ul class="s_from">
    asdasd
    <link class='active1 a123' href="http://asda.com"><a>helloasdadasdad12312</a></link>
    <link class='active2' href="http://asda1.com">asdadasdad12312</link>
    <link class='movie1' href="http://asda2.com">asdadasdad12312</link>
    </ul>
    </div>
    '''

    doc = pq(html)
    its=doc("link:first-child")
    print('第一个标签:%s'%its)
    its=doc("link:last-child")
    print('最后一个标签:%s'%its)
    its=doc("link:nth-child(2)")
    print('第二个标签:%s'%its)
    its=doc("link:gt(0)") #从零开始
    print("获取0以后的标签:%s"%its)
    its=doc("link:nth-child(2n-1)")
    print("获取奇数标签:%s"%its)
    its=doc("link:contains('hello')")
    print("获取文本包含hello的标签:%s"%its)
  7. 多选查找, 用逗号分隔

    1
    pq(html)("h1,h2") #表示查找h1和h2标签
  8. 过滤查找filter

    1
    pq(html).filter(".fisrt") #过滤出class为.fisrt的内容
  9. 使用find方法查找

    上面我们介绍的查找都是pq(html)("xxx") 列表形式, 除此之外我们还可以用使用调用函数的形式进行查找, 效果也是一样的:

    1
    2
    3
    pq(html)("#xxx") 
    #或者用
    pq(html).find("#xxx")

最后

以上只是该库的一部分使用方法, 一边用一边掌握, 我个人还是比较喜欢使用这一套解析库, 能解决大部分的应用场景

​

​

​

本文为作者原创转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

Android原生格斗游戏开发

发表于 2021-12-01

前言

昨天晚上做了个梦, 梦见我在沙…

梦里的事已经记不得了, 不过大半夜醒来后大脑异常清醒活跃, 居然在构思着一款格斗游戏

也不知道这灵光从何而来, 近大半年都没有玩游戏, 也没有做游戏相关的开发

不过在我内心深处 确实是有一个3A游戏梦, 话说格斗游戏, 算是我童年最爱吧

不管怎么着, 反正当时是越想越起劲, 心跳加速, 鼻子发痒, 差点误以为流了鼻血

既然这么刺激, 那么 就将想法变成现实吧, 开始着手进行游戏的开发, 这次平台我选择用Android原生, 为什么不直接使用第三方引擎, 主要还是想着把基础的东西捋一遍, 地基打好了, 上层建筑也就没什么难的

游戏架构设计

无论游戏还是普通应用, 都是由两个大部分组成的, 一个是场景界面, 另一个则是事件处理

我们只需要从这两个方面着手即可

场景界面

这款游戏的场景很简单, 只有一个场景, 和拳皇类似, 一个背景, 左右各一人, 就这么简单

事件处理

移动端的游戏, 我们肯定是需要在屏幕上加上触控的游戏摇杆, 来操作角色的行动

说到动作, 所有的角色都具备左右上下跑跳滑铲的动作, 因此这部分我们在代码实现的时候, 可以将其进行抽象到父类中, 让所有子类角色与生俱来

本文为作者原创转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

使用python对博客网站的文章进行爬取

发表于 2021-12-01

前言

说到爬取文章啥的, 最好使的肯定是python, python中有很多爬虫库可供我们使用, 方便快捷, 虽然工具很多, 但是大同小异, 我们只需要用好一个库就够了

接下来以掘金为例, 给大家演示一下如何爬取网站中的文章:

网站结构分析

调出浏览器控制台, 通过分析, 我们发现掘金和简书不同, 其网站中的文章链接全部都是通过接口动态请求的, 而非以Nginx容器静态存放

由于我们需要获取多篇文章,而不是单篇, 所以文章链接我们必须先拿到手, 然后再根据链接挨个将文章爬出来

文章链接获取

在推荐一栏通过上拉加载我们很轻松就能获取到请求的接口和参数, 我们只需要将其拷贝出来用python模拟请求即可

image-20211201163303973

请求接口:

1
https://api.juejin.cn/recommend_api/v1/article/recommend_cate_feed?aid=xxx&uuid=xxx

请求参数:

1
{"id_type":2,"sort_type":200,"cate_id":"6809637769959178254","cursor":"1","limit":20}

其中cursor字段表示页数, 我们可以循环递增这个字段的值来源源不断地获取数据

响应数据:

image-20211201163627506

这个article_id就是我们需要获取的值, 将这个值和https://juejin.cn/post/进行拼接就得到了文章的实际地址, 如下:

1
https://juejin.cn/post/7016520448204603423

好了, 分析完毕后, 直接上代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# coding:utf-8


import codecs
import time
import requests
import json
import sys
# 增加try except嵌套层数 避免
sys.setrecursionlimit(10000)



cache_file_name = 'temp_juejin.txt'

cache = []


def loadCache():
with codecs.open(cache_file_name, "r", "utf-8") as fr:
for line in fr:
cache.append(line)
#print(cache)
#return cache[len(cache)-1]


def startScrape():

apiUrl='https://api.juejin.cn/recommend_api/v1/article/recommend_cate_feed?aid=2608&uuid=7023196943133656589'
HEADERS = {
'User-Agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:11.0) Gecko/20100101 Firefox/11.0',

'Accept-Encoding' : 'gzip,deflate,sdch',
'Accept-Language' : 'zh-CN,zh;q=0.8',
'Content-type': 'application/json; charset=UTF-8',
'accept': 'application/json, text/plain, */*',

}
for index in range(1000):
body = {"id_type":2,"sort_type":200,"cate_id":"6809637769959178254","cursor":"1","limit":20}
body["cursor"]=str(index+1)
print(body)
r = requests.post(url=apiUrl, headers=HEADERS,data=json.dumps(body))
print(r.status_code)

res=json.loads(r.content)

with codecs.open(cache_file_name, "a", "utf-8") as f:
for item in res["data"]:
print()
id=item["article_info"]["article_id"]
link = "https://juejin.cn/post/"+id
print(link)
#if link not in cache: #判断存在或者不存在
if not any(link in s for s in cache):
cache.append(link)
f.write(link+"\n")
print("新增一条连接")
#time.sleep(10)
#切记 url不要加入换行 否则404
f.close()
time.sleep(2)




def job():

loadCache()
startScrape()




if __name__ == '__main__':
job()

执行该代码前先在同级目录下新建一个temp_juejin.txt文件, 用于存放获取到的所有文章链接, 对于初学者, 这里需要注意的是json的转换处理和请求头的设置, 如果没有使用json.dumps进行转换, 那么请求会失败, 如果请求头不加Content-type和accept或者没填对, 请求正常但是返回的不是正常的数据, 这一块是很多人极易忽视的地方

代码运行后爬取的结果如下:

image-20211201164332262

好了, 有了文章链接, 下一步我们就开始挨个文章的爬取

文章爬取

爬虫框架, 我这里使用的是PyQuery, 关于PyQuery的用法, 可参见《Python爬虫框架之PyQuery的使用》

接下来我们爬取文章的标题和内容, 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# -*- coding:utf-8 -*-

import requests
from pyquery import PyQuery as pq
import codecs
import os
import sys





sys.setrecursionlimit(1000000)

# 当前文件路径
current_path = os.path.abspath(__file__)
# 父目录
father_path = os.path.abspath(os.path.dirname(current_path) + os.path.sep + ".")



headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36',
'cookie': '__cfduid=d89ead99eeea979ea1f2a1a6243d186461600935008; Hm_lvt_2374bfdfe14a279e4a045267051b54e1=1600935010,1601459114; __yjsv3_shitong=1.0_7_8b1bac638e380ca12f87734ab2405afe2e94_300_1601470572844_223.104.3.46_aaa564dc; cf_chl_1=04a386244ad8d71; cf_chl_prog=x17; cf_clearance=f4af2dbdded649a7cf11a5a52d168289e98e68c6-1601470577-0-1zd4e21871z8a534313z279abd70-150; Hm_lpvt_2374bfdfe14a279e4a045267051b54e1=1601470578',
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',

}

session = requests.Session()
session.headers.update(headers)

origin_url_cache_file_name="temp_juejin.txt"

originUrlCache = []

# 将文件中内容按行加载至列表中
def loadCache(filename, filter_text):
cache = []
with codecs.open(filename, "r", "utf-8") as fr:
if filter_text:
for line in fr:
if filter_text in line:
cache.append(line.replace("\n", ""))
else:
for line in fr:
cache.append(line.replace("\n", ""))
return cache



def get_article_by_url(url):
rep = session.get(url)
d=pq(rep.text)
title = d('h1').text()
content = d('.markdown-body').html()
return title, content

def startScrape():
for link in originUrlCache:

print(link + "\n")
title, content = get_article_by_url(link)
print(title + "\n")
print(content)





if __name__ == '__main__':

#将需要爬取的url加载到内存中
originUrlCache=loadCache("{parent}/{filename}".format(parent=father_path,filename=origin_url_cache_file_name), None)
#开始爬取文章
startScrape()

但是打印结果全部为None, 去控台一查发现掘金文章页面内容是通过js动态渲染的, 如果直接获取html是无法通过PyQuery获取到我们想要的内容的, 那这咋办?

想一下, 如果我们能拿到渲染完成后的html, 然后再通过PyQuery进行查找, 不就完事了

问题在于如何获取到渲染完成后的页面源码, 单纯的Get请求肯定是不行的, 我们需要模拟浏览器渲染才行,

这个时候我们就需要用到一个Web自动化框架, 也就是大名鼎鼎的selenium, 它可以模拟真实的浏览器访问和查找甚至是点击操作, 这里我们只需要利用它得到页面源码即可, 关于selenium的详细使用, 可参见《Web自动化框架selenium的介绍与使用》

于是代码修改成如下模样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# -*- coding:utf-8 -*-

import requests
from pyquery import PyQuery as pq
import codecs
import os
import sys

from selenium import webdriver



chrome_options = webdriver.ChromeOptions()
# 使用headless无界面浏览器模式
chrome_options.add_argument('--headless') #增加无界面选项
chrome_options.add_argument('--disable-gpu') #如果不加这个选项,有时定位会出现问题

browser = webdriver.Chrome(chrome_options=chrome_options)




sys.setrecursionlimit(1000000)

# 当前文件路径
current_path = os.path.abspath(__file__)
# 父目录
father_path = os.path.abspath(os.path.dirname(current_path) + os.path.sep + ".")




origin_url_cache_file_name="temp_juejin.txt"

originUrlCache = []

# 将文件中内容按行加载至列表中
def loadCache(filename, filter_text):
cache = []
with codecs.open(filename, "r", "utf-8") as fr:
if filter_text:
for line in fr:
if filter_text in line:
cache.append(line.replace("\n", ""))
else:
for line in fr:
cache.append(line.replace("\n", ""))
return cache



def get_article_by_url(url):
browser.get(url)
d = pq(browser.page_source)
title = d('h1').text()
content = d('.markdown-body').html()
return title, content

def startScrape():
for link in originUrlCache:

print(link + "\n")
title, content = get_article_by_url(link)
print(title + "\n")
print(content)





if __name__ == '__main__':

#将需要爬取的url加载到内存中
originUrlCache=loadCache("{parent}/{filename}".format(parent=father_path,filename=origin_url_cache_file_name), None)
#开始爬取文章
startScrape()

该代码运行的前提是需要安装谷歌浏览器

运行时如果提示This version of ChromeDriver only supports Chrome version, 那么说明浏览器版本和驱动版本不一致, 需要下载与浏览器相匹配的驱动

查看谷歌浏览器版本:

在这里插入图片描述

然后下载驱动:

ChromeDriver下载地址

在这里插入图片描述

将下载的驱动解压到以下目录:

1
2
Win:复制webdriver到Python安装目录下
Mac:复制webdriver到/usr/local/bin目录下

至此, 我们成功爬取到指定文章地址的标题和内容

既然获取到了想要的数据, 那么接下来你可以考虑将其存放到本地, 或者上传到你的wordpress

关于wordpress文章的上传,可参考文章《如何将python采集到的文章保存到wordpress》

文章上传到wordpress

秉着善始善终的原则, 以上面的代码为例给大家补充上上传wordpress后的最终代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# -*- coding:utf-8 -*-

import time
from lxml import html
import requests
from pyquery import PyQuery as pq
import codecs
import os
import sys
from wordpress_xmlrpc import Client, WordPressPost
from wordpress_xmlrpc.methods.posts import GetPosts,NewPost

from selenium import webdriver
import time


chrome_options = webdriver.ChromeOptions()
# 使用headless无界面浏览器模式
chrome_options.add_argument('--headless') #增加无界面选项
chrome_options.add_argument('--disable-gpu') #如果不加这个选项,有时定位会出现问题

browser = webdriver.Chrome(chrome_options=chrome_options)

sys.setrecursionlimit(1000000)

# 当前文件路径
current_path = os.path.abspath(__file__)
# 父目录
father_path = os.path.abspath(os.path.dirname(current_path) + os.path.sep + ".")

pushed_cache_file_name="temp_juejin_pushed_url.txt"
origin_url_cache_file_name="temp_juejin.txt"




pushedCache = []
originUrlCache = []
wp = Client('http://您的域名/xmlrpc.php', 'wordpress用户名', 'wordpress登录密码')

# 将文件中内容按行加载至列表中
def loadCache(filename, filter_text):
cache = []
with codecs.open(filename, "r", "utf-8") as fr:
if filter_text:
for line in fr:
if filter_text in line:
cache.append(line.replace("\n", ""))
else:
for line in fr:
cache.append(line.replace("\n", ""))
return cache



def push_article(post_title,post_content_html):
post = WordPressPost()
post.title = post_title
post.slug = post_title
post.content = post_content_html
post.terms_names = {
'post_tag': post_title.split(" "),
'category': ["itarticle"]
}
post.post_status = 'publish'
wp.call(NewPost(post))

def get_article_by_url(url):
browser.get(url)
d = pq(browser.page_source)
browser.quit
title = d('h1').text()
content = d('.markdown-body').html()
return title, content

def startScrape():
for link in originUrlCache:
if not any(link in s for s in pushedCache):
print(link + "\n")
title, content = get_article_by_url(link)
print(title + "\n")
print(content)
push_article(title,content)
time.sleep(2)
with codecs.open("{parent}/{filename}".format(parent=father_path,filename=pushed_cache_file_name), 'a', "utf-8") as fw:
fw.write(link + "\n")
fw.close()




if __name__ == '__main__':
#将已经爬取过的url加载到内存中
pushedCache=loadCache("{parent}/{filename}".format(parent=father_path,filename=pushed_cache_file_name), None)
#将需要爬取的url加载到内存中
originUrlCache=loadCache("{parent}/{filename}".format(parent=father_path,filename=origin_url_cache_file_name), None)
#开始爬取文章
startScrape()

只要将域名, 用户名和密码替换成你自己的就行

注意wordpress_xmlrpc库的安装:

1
pip install python-wordpress-xmlrpc

补充

另外 上面获取文章链接环节也可以直接使用selenium进行获取, 虽然不如调接口来的快, 但是碰上接口被加密的情况, 那么selenium的方式能快速解决

本文为作者原创转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

越来越火的网络请求Fetch和Axios到底有什么区别

发表于 2021-11-30

在这几天由于编写脚本等一系列原因,不知怎么的突然发现现在很多主流的网站已经大量开始使用Fetch进行网络请求,感觉再不学习Fetch就要Out了,所以我花了一些时间专门去研究了一下关于Fetch的相关知识,发现Fetch被讨论的并不多,很多都是一年前甚至两年前的文章,大多数文章最后得出来的结论几乎都是Axios比Fetch好用。

事实确实如此,就我个人的体验来讲,Axios使用体验确实优于Fetch,那么为什么目前在很多大公司的网站上面都开始使用Fetch进行网络请求,带着这个疑问,我又去查找了很多资料,同时又自行将同样的请求使用Axios和Fetch进行尝试,最后得出一个结论:Fetch的优势仅仅在于浏览器原生支持。

对的,其实Fetch比起Axios来讲几乎没有任何优势(除了浏览器原生支持),Axios各个方面都比Fetch好用,Fetch要想实现Axios的一些功能还需要手动进行封装。

我截取了几个比较大的网站的请求图:

掘金:

img

YouTube:

img

知乎:

img

需要注意的是:Axios是对XMLHttpRequest的封装,而Fetch是一种新的获取资源的接口方式,并不是对XMLHttpRequest的封装。

它们最大的不同点在于Fetch是浏览器原生支持,而Axios需要引入Axios库。

1. 火热程度

虽然无法进行直观的比较,但是我们可以从npm包下载量来看:

img

因为Node环境下默认是不支持Fetch的,所以必须要使用node-fetch这个包,而这个包的周下载量一路攀升,可以看到已经来到了每周2千多万的下载量。这还仅仅是Node环境下,浏览器则是默认支持不需要第三方包。

img

上面是Axios的下载量,可以看到也是一路攀升,Axios封装的各种方法确实非常的好用。


本篇文章着重会从下面几项内容进行比较:

  • 兼容性
  • 基本语法
  • 响应超时
  • 对数据的转化
  • HTTP拦截器
  • 同时请求

2. 兼容性问题

Axios可以兼容IE浏览器,而Fetch在IE浏览器和一些老版本浏览器上没有受到支持,但是有一个库可以让老版本浏览器支持Fetch即它就是whatwg-fetch,它可以让你在老版本的浏览器中也可以使用Fetch,并且现在很多网站的开发都为了减少成本而选择不再兼容IE浏览器。

注意:在比较旧的浏览器上面可能还需要使用promise兼容库。

各个浏览器对Fetch的兼容:

img

3. 请求方式

下面我们来看一下如何使用Axios和Fetch进行请求。

Axios:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const options = {
url: "http://example.com/",
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json;charset=UTF-8",
},
data: {
a: 10,
b: 20,
},
};

axios(options).then((response) => {
console.log(response.status);
});

Fetch:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const url = "http://example.com/";
const options = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json;charset=UTF-8",
},
body: JSON.stringify({
a: 10,
b: 20,
}),
};

fetch(url, options).then((response) => {
console.log(response.status);
});

其中最大的不同之处在于传递数据的方式不同,Axios是放到data属性里,以对象的方式进行传递,而Fetch则是需要放在body属性中,以字符串的方式进行传递。

4. 响应超时

Axios的相应超时设置是非常简单的,直接设置timeout属性就可以了,而Fetch设置起来就远比Axios麻烦,这也是很多人更喜欢Axios而不太喜欢Fetch的原因之一。

1
2
3
4
5
6
7
8
9
10
11
12
13
axios({
method: "post",
url: "http://example.com/",
timeout: 4000, // 请求4秒无响应则会超时
data: {
firstName: "David",
lastName: "Pollock",
},
})
.then((response) => {
/* 处理响应 */
})
.catch((error) => console.error("请求超时"));

Fetch提供了AbortController属性,但是使用起来不像Axios那么简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const controller = new AbortController();

const options = {
method: "POST",
signal: controller.signal,
body: JSON.stringify({
firstName: "David",
lastName: "Pollock",
}),
};
const promise = fetch("http://example.com/", options);

// 如果4秒钟没有响应则超时
const timeoutId = setTimeout(() => controller.abort(), 4000);

promise
.then((response) => {
/* 处理响应 */
})
.catch((error) => console.error("请求超时"));

5. 对数据的转化

Axios还有非常好的一点就是会自动对数据进行转化,而Fetch则不同,它需要使用者进行手动转化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// axios
axios.get("http://example.com/").then(
(response) => {
console.log(response.data);
},
(error) => {
console.log(error);
}
);

// fetch
fetch("http://example.com/")
.then((response) => response.json()) // 需要对响应数据进行转换
.then((data) => {
console.log(data);
})
.catch((error) => console.error(error));

Fetch提供的转化API有下面几种:

  • arrayBuffer()
  • blob()
  • json()
  • text()
  • formData()

使用Fetch时你需要清楚请求后的数据类型是什么,然后再用对应的方法将它进行转换。

Fetch可以通过一些封装实现Axios的自动转化功能,至于如何实现由于我没有去研究过所以就不再这里多嘴,不过实现起来应该不难,但是要将实现过程写的健壮就需要花费一定的时间。

6. HTTP拦截器

Axios的一大卖点就是它提供了拦截器,可以统一对请求或响应进行一些处理,相信如果看过一个比较完整的项目的请求封装的话,一定对Axios的拦截器有一定的了解,它是一个非常重要的功能。

使用它可以为请求附加token、为请求增加时间戳防止请求缓存,以及拦截响应,一旦状态码不符合预期则直接将响应消息通过弹框的形式展示在界面上,比如密码错误、服务器内部错误、表单验证不通过等问题。

1
2
3
4
5
6
7
8
9
axios.interceptors.request.use((config) => {
// 在请求之前对请求参数进行处理
return config;
});

// 发送GET请求
axios.get("http://example.com/").then((response) => {
console.log(response.data);
});

而Fetch没有拦截器功能,但是要实现该功能并不难,直接重写全局Fetch方法就可以办到。

1
2
3
4
5
6
7
8
9
10
11
12
fetch = ((originalFetch) => {
return (...arguments) => {
const result = originalFetch.apply(this, arguments);
return result.then(console.log("请求已发送"));
};
})(fetch);

fetch("http://example.com/")
.then((response) => response.json())
.then((data) => {
console.log(data);
});

7. 同时请求

同时请求在项目中用的不多,但是偶尔可能会用到。

Axios:

1
2
3
4
5
6
7
8
9
10
axios
.all([
axios.get("https://api.github.com/users/iliakan"),
axios.get("https://api.github.com/users/taylorotwell"),
])
.then(
axios.spread((obj1, obj2) => {
...
})
);

Fetch:

1
2
3
4
5
6
7
8
9
10
11
Promise.all([
fetch("https://api.github.com/users/iliakan"),
fetch("https://api.github.com/users/taylorotwell"),
])
.then(async ([res1, res2]) => {
const a = await res1.json();
const b = await res2.json();
})
.catch((error) => {
console.log(error);
});

8. 浏览器原生支持

Fetch唯一碾压Axios的一点就是现代浏览器的原生支持。

本着负责的态度(其实是因为这篇文章写得比较困难…因为我对Fetch的研究不深)在这几天,我多次尝试使用Fetch,习惯后觉得还挺好用的,最主要是浏览器原生就支持,不像Axios需要引入一个包,而且需要即时测试某些接口直接在Chrome浏览器中使用Fetch进行请求,尤其是编写爬虫或脚本的时候,你在当前网页打开Chrome的控制台使用Fetch几乎不需要什么配置就可以直接进行请求。

img

上图是在知乎打开Chrome控制台然后调用知乎个人数据API,可以看到能够成功的拿到数据。

9. 最后

Fetch可以实现所有Axios能够实现的功能,但是需要自行进行封装,如果不喜欢折腾直接在项目中使用Axios是一个非常明智的选择,这完全取决于你是否愿意使用浏览器内置API。

有时候新技术逐渐取代老技术是一个必然趋势,所以Fetch有一天终将会取代XMLHttpRequest,也许之后Axios库会改为使用Fetch请求

本文为转载文章 点击进入原文

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

海底掘金自动化挖矿实现

发表于 2021-11-29

前言

最近掘金出了一款小游戏叫做海底掘金, 挖取矿石可以兑换周边礼品

我眼馋那款游戏机已久, 于是乎就玩起了这款游戏, 不过每天手动操作实在太过劳神, 于是, 我打算将其流程自动化

所有的数据都是离不开接口, 我们只需要模拟接口请求即可

那么首先要做的就是分析游戏中每一个动作都发送了什么数据

动作分析

  1. 上移

    1
    {"command":["U"]}

    image-20211129115453501

  2. 下移

    1
    {"command":["D"]}

    image-20211129115518866

  3. 左移

    1
    {"command":["L"]}

    image-20211129115538071

  4. 右移

    1
    {"command":["R"]}

    image-20211129115556431

  5. 跳跃

    1
    2
    3
    4
    5
    6
    7
    8
    //上跳跃
    {command: ["8"]}
    //下跳跃
    {command: ["2"]}
    //左跳跃
    {command: ["4"]}
    //右跳跃
    {command: ["6"]}

    image-20211129120520331

  6. 一层循环2次上移

    1
    {command: [{times: 2, command: ["U"]}]}
    • times: 表示循环次数

    image-20211129115642187

  7. 两层循环

    1
    {command: [{times: 2, command: [{times: 3, command: ["D"]}]}]}

    image-20211129115708099

    多层循环则多层嵌套

接口分析

  1. 动作接口

    1
    https://juejin-game.bytedance.com/game/sea-gold/game/command?uid=xxx&time=xxx
  2. 游戏结束接口

    1
    https://juejin-game.bytedance.com/game/sea-gold/game/over?uid=xxx&time=xxx
  3. 游戏开始接口

    1
    https://juejin-game.bytedance.com/game/sea-gold/game/start?uid=xxx&time=xxx
  4. Token获取接口

    1
    https://juejin.cn/get/token

    这个接口获取到的值其实就是其他接口中请求头authorization的值, 具体往下看

  5. 获取矿石数量相关信息

    1
    https://juejin-game.bytedance.com/game/sea-gold/home/info?uid=xxx&time=xxx

浏览器控台自动化实现

我们跳过登录验证操作, 先从简单的控台自动化入手, 实现动作的自动化, 有效避免作弊检测

比如我们想要实现以下动作的反复执行

image-20211129121755381

那么发送的数据如下:

1
{"command":[{"times":10,"command":["L","2","R",{"times":10,"command":["L","4","D",{"times":10,"command":["R","D","2"]}]}]}]}

这一步我们可以直接从浏览器控制台里拷贝出来, 不用自己计算, 方便快捷准确

然后结合动作接口和请求头进行模拟请求即可

如下请求头

  • Content-type
  • authorization (关键)
  • x-tt-gameid (关键)
  • accept

以上请求头数据直接从控制台拷贝

代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
await (async function autoRun(){
//以下三个字段值替换成你自己的就行
let uid = 'xxx';// 你的uid
let authorization = 'xxx'; // request headers中的authorization
let gameid = 'xxx'; // request headers中的x-tt-gameid


let params = {"command":[{"times":10,"command":["L","2","R",{"times":10,"command":["L","4","D",{"times":10,"command":["R","D","2"]}]}]}]};

let datarus = await fetch('https://juejin-game.bytedance.com/game/sea-gold/game/command?uid=' + uid + '&time=' + Date.parse(new Date()), {
method: 'POST',
credentials: "include",
headers: {
'Content-type': 'application/json; charset=UTF-8',
'authorization': authorization,
'accept': 'application/json, text/plain, */*',
'content-length': JSON.stringify(params).length,
'x-tt-gameid': gameid,
},
body: JSON.stringify(params)
}).then(async (res) => {
return res.json();
});

return datarus;

})();

注意, 每一局的gameId是不一样的, 使用相同的gameId会报游戏异常, 这种操作大家要尽量避免, 谁知道官方有没有利用这个来判定玩家是否作弊呢, 谨慎为上

请求成功后响应数据如下:

1
{"data":{"appendMapData":[0,0,133,0,0,0,27,122,6,0,0,22,6,0,0,6,135,6,0,21,21,0,0,122,0,0,0,0,23,0,6,6,0,0,6,0,25,0,0,121,0,6,6,0,29,0,6,6,0,151,6,0,0,6,27,0,0,6,0,0,0,0,125,6,0,0,0,5,0,135,113,143,0,6,131,0,0,102,101,151,6,6,0,0,0,0,3,0,0,6,0,6,6,6,122,0,6,6,0,6,6,0,0,0,29,0,6,0,6,0,0,101,6,0,24,6,6,0,0,0,6,0,6,6,6,132,0,6,0,0,0,0,6,0,5,6,6,131,114,0,0,0,0,0,0,6,6,0,6,0,6,6,6,0,104,0,6,0,4,0,0,0,6,125,6,113,0,0,0,6,0,6,6,0,0,0,6,6,0,0,6,0,5,6,5,121,28,0,0,24,0,0,6,151,0,0,141,6,0,24,0,6,0,0,6,6,0,0,131,6,105,6,0,0,27,113,6,0,6,0,0,143,0,0,101,0,29,0,0,6,0,0,0,133,6,6,25,24,151,29,6,104,6,0,6,6,5,0,0,131,6,131,6,6,0,0,0,6,0,28,133,0,124,6,6,0,0,0,131,0,22,0,0,0,0,0,0,0,22,131,6,6,0,6,6,24,124,6,0,0,101,6,6,0,0,0,103,0,6,26,0,0,0,0,103,0,6,0,6,131,0,6,0,6,6,6,0,141,6,29,0,0,0,6,0,3,6,0,6,6,6,0,0,114,0,0,6,0,0,6,0,6,0,6,6,6,0,0,28,23,6,102,0,115,4,6,6,0,6,27,6,6,0,6,6,0,0,24,0,3,6,0,0,143,0,29,0,26,0,4,6,105,0,0,0,6,0,21,132,21,0,0,0,6,4,103,6,0,27,6,0,24,24,0,6,104,0,104,0,0,6,6,152,6,0,6,0,0,0,0,0,0,0,6,24,6,6,23,143,0,0,24,0,6,0,6,0,0,0,151,0,26,0,6,0,0,0,0,6,0,0,0,0,6,135,0,6,6,0,0,0,0,29,6,0,0,6,0,24,25,6,0,0,0,0,0,6,0,0,6,0,6,152,28,6,25,6,0,6,0,6,6,0,0,0,25,0,0,6,122,6,0,0,6,112,6,0,0,124,0,0,0,0,25,0,6,6,6,6,27,112,24,121,6,6,0,6,0,6,0,6,115,0,22,6,6,6,115,6,6,101,0,27,5,105,0,0,0,0,0,0,0,6,0,0,0,3,6,6,0,0,0,0,6,0,0,0,0,26,6,6,0,0,6,6,6,6,6,0,6,0,6,6,0,0,0,0,6,0,6,6,0,0,3,5,104,0,0,122,27,0,0,0,0,23,0,0,0,0,0,4,23,25,6,0,153,0,0,0,0,0,6,0,0,0,6,6,6,0,6,0,4,0,6,6,0,3,0,112,0,0,0,0,0,0,6,0,113,0,0,0,6,6,21,0,6,124,0,6,115,0,0,23,132,0,0,6,0,6,6,0,6,6,6,0,6,0,6,0,0,6,6,0,6,6,26,0,134,0,6,6,6,0,0,0,0,0,0,0,0,0,6,6,6,113,111,0,6,0,6,0,0,0,0,0,124,0,0,6,0,22,6,6,24,22,6,104,0,125,0,0,6,6,0,6,6,0,0,121,6,0,6,0,0,6,0,6,6,0,0,0,0,0,25,0,0,6,0,6,6,6,111,0,0,6,0,5,0,0,0,6,6,6,0,6,0,123,6,0,6,25,0,0,6,113,0,0,6,0,0,0,0,0,6,0,0,6,122,6,134,0,0,6,0,134,0,6,0,0,0,6,103,0,24,0,6,0,6,0,0,0,0,0,6,6,0,132,6,0,6,103,6,0,6,28,26,6,0,111,6,131,0,27,27,0,6,6,0,102,0,0,6,0,6,0,0,6,21,6,0,6,0,6,121,26,0,6,0,6,0,0,0,0,0,0,6,0,24,0,0,0,6,0,4,0,22,23,28,0,6,6,111,153,131,0,0,0,0,0,6,27,0,0,23,0,0,0,0,6,0,0,104,0,0,0,104,6,0,0,0,6,6,0,0,6,29,6,0,0,0,0,6,6,0,0,6,6,0,0,0,23,6,0,0,0,6,0,113,6,0,6,6,6,0,0,0,29,152,0,0,0,153,0,6,6,0,6,6,0,26,6,6,6,151,21,0,21,6,0,21,6,6,6,0,6,0,0,0,22,6,6,6,0,6,0,21,6,6,0,6,6,22,0,5,0,6,0,0,0,0,0,28,6,0,6,114,0,6,0,0,6,0,23,0,0,6,0,0,114,6,6,0,0,0,6,0,0,0,0,6,0,6,6,0,0,0,0,0,0,0,24,0,0,0,6,134,0,0,0,131,0,0,0,0,6,6,25,0,0,6,5,0,104,0,6,6,0,0,6,0,0,0,6,0,6,101,0,0,6,0,6,0,0,0,125,0,0,6,134,0,6,6,27,0,6,0,131,6,0,0,6,0,0,6,0,123,0,0,0,0,6,0,0,6,6,6,6,0,6,0,6,0,0,0,25,0,0,0,0,0,6,0,0,6,0,6,0,0,0,6,0,6,6,6,114,6,6,0,0,0,0,0,6,0,0,6,124,6,6,0,0,154,6,0,152,0,0,22,6,6,6,0,6,26,0,0,111,0,133,29,0,0,6,6,104,133,0,0,6,26],"curPos":{"x":5,"y":210},"blockData":{"moveUp":29,"moveDown":30,"moveLeft":14,"moveRight":27,"jump":7,"loop":15},"gameDiamond":112},"code":0,"message":"success","logId":"xxxx","serviceTime":1638159487113}

注意看返回的gameDiamond这个字段, 我们执行一次接口立马就获取了112个矿石, 也就是说我们掉完接口立马结束游戏, 那么这矿石就到手了, 省去了游戏执行的时间

在同一局中, 我们可以反复调接口, 直到道具不足为止, 如果道具不足就重开一局再过一遍之前的代码

到这里 问题就来了, 每一局的x-tt-gameid都不一样, 每新开一局都要拷贝一次太繁琐了,接下来我们解决x-tt-gameid的问题

签名破解

接下来全局翻找源码, 从js源码中, 我大致猜测这个x-tt-gameid是由ES256算法生成accessToken, 找到如下代码:

1
h.default.sign({gameId:this.gameId,time:t},"-----BEGIN EC PARAMETERS-----\nBggqhkjOPQMBBw==\n-----END EC PARAMETERS-----\n-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIDB7KMVQd+eeKt7AwDMMUaT7DE3Sl0Mto3LEojnEkRiAoAoGCCqGSM49\nAwEHoUQDQgAEEkViJDU8lYJUenS6IxPlvFJtUCDNF0c/F/cX07KCweC4Q/nOKsoU\nnYJsb4O8lMqNXaI1j16OmXk9CkcQQXbzfg==\n-----END EC PRIVATE KEY-----\n",{algorithm:"ES256",expiresIn:2592e3,header:{alg:"ES256",typ:"JWT"}});return console.log("token",e)

当然也只是猜测, 需要进行验证

为了方便测试, 我将代码测试从控台转移到nodejs环境中

对于nodejs不太熟悉的朋友, 我这里简单介绍一下:

nodejs可以通俗理解为是模拟了一个浏览器的环境, 相当于java中的jre, 以前js代码只能在浏览器中执行, 有了nodejs环境, 我们就可以脱离浏览器随处运行js代码啦

Nodejs官网

好了 下载安装完nodejs之后, 安装jsonwebtoken模块用于jwt的生成

1
npm install jsonwebtoken

然后运行以下代码测试:

1
2
3
4
5
const jwt = require('jsonwebtoken');
const privatekey = "-----BEGIN EC PARAMETERS-----\nBggqhkjOPQMBBw==\n-----END EC PARAMETERS-----\n-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIDB7KMVQd+eeKt7AwDMMUaT7DE3Sl0Mto3LEojnEkRiAoAoGCCqGSM49\nAwEHoUQDQgAEEkViJDU8lYJUenS6IxPlvFJtUCDNF0c/F/cX07KCweC4Q/nOKsoU\nnYJsb4O8lMqNXaI1j16OmXk9CkcQQXbzfg==\n-----END EC PRIVATE KEY-----\n"

console.log(jwt.decode("eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJnYW1lSWQiOiIyMDIxLTExLTI2IDA5OjUwOjIwIiwidGltZSI6IjE2Mzc5MTMyNjIwMTkiLCJpYXQiOjE2Mzc5MTMyNjIsImV4cCI6MTY0MDUwNTI2Mn0.2SWFcrvSMGzRIhsZe6ny0ugj5eoJnb4DlGGudFzUgvwPTqbeZaSa2FG5jYF5VPOXfv7Ye0TlYPt3VmviclOGKQ")
)

解码得到:

1
2
3
4
5
6
{
gameId: '2021-11-26 09:50:20',
time: '1637913262019',
iat: 1637913262,
exp: 1640505262
}

至此, 我们既验证了密钥的正确性, 同时得到了加密参数, 而这个time则是当前时间毫秒值

那接下来我们需要在游戏中实际验证一下才行, 验证步骤如下:

  1. 调用游戏开始接口获取返回的gameId
  2. 将gameId进行ES256加密得到签名
  3. 调用动作接口, 并将签名放入x-tt-gameid请求头中
  4. 请求成功, 验证ok

好了 我们的第一阶段的破解工作已完毕, 接下来就是对代码进行整理, 实现自动化运行

由于代码转移到nodejs上之后使用fetch进行网络请求代码一直报错, 因此请求方式改为axios

这里多一嘴, 虽然nodejs模拟了浏览器的环境, 但是浏览器内置的一些API, nodejs上不一定也内置了, 要用的话的npm手动安装, 比如fetch是浏览器内置的API, 控台直接可以使用, 如果要在node上用的话可以执行以下指令安装:

1
npm install node-fetch

然后代码中引用:

1
const fetch = require('node-fetch');

同样axios也是:

1
npm install axios

代码引用:

1
const axios = require('axios');

关于fetch和axios两者的实现原理和区别, 有兴趣的可自行百度, 这里就不展开介绍了

回过头来, 整理后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
const jwt = require('jsonwebtoken');
const axios = require('axios');


const uid = 'xxx';// 你的uid
let authorization = 'xxx'; // request headers中的authorization


let currentTime = Date.parse(new Date())
let gameId = "2021-11-30 10:36:12"
function getAccesssToken() {

const privatekey = "-----BEGIN EC PARAMETERS-----\nBggqhkjOPQMBBw==\n-----END EC PARAMETERS-----\n-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIDB7KMVQd+eeKt7AwDMMUaT7DE3Sl0Mto3LEojnEkRiAoAoGCCqGSM49\nAwEHoUQDQgAEEkViJDU8lYJUenS6IxPlvFJtUCDNF0c/F/cX07KCweC4Q/nOKsoU\nnYJsb4O8lMqNXaI1j16OmXk9CkcQQXbzfg==\n-----END EC PRIVATE KEY-----\n"
currentTime = Date.parse(new Date())
const payload = { "gameId": gameId, "time": currentTime }

const accessToken = jwt.sign(payload, privatekey, {
expiresIn: "240h",
algorithm: "ES256",
});
//console.log(accessToken)
return accessToken
}


//游戏开始
async function gameStart() {



//选择 click这个角色 钻石有加成
let params = { "roleId": 2 };

const options = {
url: 'https://juejin-game.bytedance.com/game/sea-gold/game/start?uid=' + uid + '&time=' + Date.parse(new Date()),
method: "POST",
credentials: "include",
headers: {
'Content-type': 'application/json; charset=UTF-8',
'authorization': authorization,
'accept': 'application/json, text/plain, */*',
'content-length': JSON.stringify(params).length,
'x-tt-gameid': '',
'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Mobile Safari/537.36'
},
data: params,
};

return await axios(options).then((response) => {
console.log(response.data);
if (response.data.code == 4007) {
//游戏正在执行 则结束游戏
// gameOver()

} else if (response.data.code == 0) {
//成功开始
gameId = response.data.data.gameId


}
});


}
//执行游戏动作
async function gameRun() {


let gameid = getAccesssToken(); // request headers中的x-tt-gameid



let paramsList = [{ "command": [{ "times": 10, "command": ["L", "2", "R", { "times": 10, "command": ["L", "4", "D", { "times": 10, "command": ["R", "D", "2"] }] }] }] },
{ "command": [{ "times": 10, "command": ["L", "D", "R", { "times": 10, "command": ["L", "4", "D", { "times": 10, "command": ["R", "D", "2"] }] }] }] },
{ "command": [{ "times": 10, "command": ["U", "4", "L", { "times": 10, "command": ["L", "4", "D", { "times": 10, "command": ["R", "D", "2"] }] }] }] }
];

let params = paramsList[Math.floor(Math.random() * paramsList.length)];

const options = {
url: 'https://juejin-game.bytedance.com/game/sea-gold/game/command?uid=' + uid + '&time=' + currentTime,
method: "POST",
credentials: "include",
headers: {
'Content-type': 'application/json; charset=UTF-8',
'authorization': authorization,
'accept': 'application/json, text/plain, */*',
'content-length': JSON.stringify(params).length,
'x-tt-gameid': gameid,
},
data: params,
};

return await axios(options).then((response) => {
console.log(response.data);

if (response.data.code == 0) {
//成功

return response.data.data.gameDiamond

} else {
//4009 代码块不足
return 0
}
});

}
//结束游戏
async function gameOver() {



let params = { "isButton": 1 };

const options = {
url: 'https://juejin-game.bytedance.com/game/sea-gold/game/over?uid=' + uid + '&time=' + Date.parse(new Date()),
method: "POST",
credentials: "include",
headers: {
'Content-type': 'application/json; charset=UTF-8',
'authorization': authorization,
'accept': 'application/json, text/plain, */*',
'content-length': JSON.stringify(params).length,
},
data: params,
};

return await axios(options).then((response) => {
console.log(response.data);
if (response.data.code == 0) {
//成功

return response.data.data.todayDiamond>=response.data.data.todayLimitDiamond

} else {

return true
}
});


}
async function delay(time) {

var start = Number(new Date());
while (start + time * 200 > Number(new Date())) { }
}
async function start() {
//开始游戏-->游戏执行-->游戏结束
for (let index = 0; index < 100; index++) {
console.log("倒计时:" + index)
await gameStart()
let time = await gameRun()


//同步延迟结束游戏 根据获取的金币而定 金币越多 延迟时间越长
await delay(time)
let info=await gameOver()
//金币达到上限 停止游戏
if(info){
return
}
}


}

start()

将以上代码拷贝到你的本地文件中, 填入你的游戏authorization和uid, 然后每天用node运行一遍即可

到这里, 第一阶段的自动化已然实现

程序定时执行

每天手动跑代码太费事, 稍不留神就有可能某天给漏了, 尤其是周六日不开电脑的时候岂不是没法执行脚本了

这时 我们可以考虑放到服务器中每天定时执行, 365天风雨无阻, 推荐大家使用github或者travis ci免费服务部署

我这里手上刚好有一台阿里云的服务器, 因此直接用服务器跑, 这里我使用的是crontab定时程序, 关于crontab的用法, 可参见《Linux Crontab 命令安装和使用教程:在 VPS 上设置定时任务》

以ubuntu为例具体操作步骤如下:

  1. 将本地脚本拷贝至服务器

    1
    scp 脚本文件 root@服务器IP:/root

    注意:冒号前后不能有空格, 否则会提示目录不存在

  2. 登录服务器配置crontab任务

    执行crontab -e进入cron编辑界面, 将以下定时代码写入

    1
    0 8 * * *  /usr/bin/node /root/jwt_test >>/root//log/jwt_test_$(date +\%Y-\%m-\%d-\%H).log 2>&

    表示每天8点执行该脚本

  3. 安装nodejs及相关依赖

    如果服务器已经安装nodejs可忽略该步操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //更新源
    sudo apt-get update
    //安装nodejs
    sudo apt-get install -y nodejs
    //安装npm包管理器
    sudo apt-get install npm
    //安装脚本依赖
    npm install jsonwebtoken
    npm install axios
  4. 配置完毕

本文为作者原创转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

1…192021…50

乱码三千

android程序员一枚,擅长java,kotlin,python,金融投资,欢迎交流~

493 日志
143 标签
RSS
© 2025 乱码三千
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%