我用 Cypress 做 Xbox 网站爬虫,一点也不后悔。
和许多人一样,我也想入手一台新款 Xbox。但除了极少数勤奋的网购达人之外,我至今为止都未能如愿,反而不断看到类似这样的图片:
那么,一个有进取心/走投无路的网络开发人员会怎么做呢?当然是自己构建警报系统啦!
网络爬虫本身就是一个相当简单的应用,通常也是这类应用的理想用例。但我希望为其添加一些视觉元素,以确保不会出现误报,而且我个人也更喜欢用户界面而不是裸代码(毕竟我在Stackery工作)。此外,过去一个月左右我一直在使用Cypress测试套件,非常喜欢它在前端测试方面的应用,因此我一直在寻找更多将其应用到我的项目中的方法。
现在,我应该说:我猜这并不是Cypress.io 的开发人员在构建基于浏览器的测试库时所设想的用例,但正如一句名言所说,“你可以发明一把锤子,但你无法阻止第一个用户用它来敲自己的脑袋¹ ”。
所以,事不宜迟,让我们捶胸顿足,赶紧去买一台 Xbox 吧!
设置:注册一个 Cypress 账户
Cypress 有一个非常棒的功能,允许你在其 Web 应用中查看自动化测试运行的视频。要使用此功能,你需要一个免费的开发者帐户:
- 前往Cypress 注册页面并创建账户
- 进入控制面板后,创建一个新项目。项目名称可以随意取,比如“Xbox股票抓取器”、“测试灾难”等等。我通常把项目名称和代码仓库名称一样,因为我的脑回路就是这样。
- 现在,你需要记下这些信息
projectId以及记录key,因为稍后会用到。
为你的爬虫创建一个无服务器架构
由于商店库存变化频繁,我们需要定期运行爬虫程序——初始阶段每小时运行一次,当然您可以根据需要轻松调整运行频率。当然,我们希望实现这些运行的自动化,因为关键在于您还有自己的生活,不想频繁刷新网页。是不是只有我一个人觉得这很像一个理想的无服务器用例?不只是我这么觉得?我就知道!
我最初想用 Lambda 函数来运行整个程序,但经过几个小时的摸索,我发现这真的非常非常难,而且最终并不值得,因为CodeBuild 作业就能很好地完成这项工作。
我将使用 Stackery 构建我的技术栈,所以这些说明将围绕这个工作流程展开。这部分是可选的,因为你也可以在 AWS 控制台中完成这些操作,但我喜欢用更简单的方式,而 Stackery 的简易模式2就是无服务器架构。
- 如果您还没有 Stackery 帐户,请创建一个免费的 Stackery 帐户。
-
通常情况下,您需要在设计画布中逐个添加资源,但由于此堆栈主要基于 CodeBuild 作业和相关角色,因此可以更轻松地复制粘贴 AWS SAM 模板,如下所示:
在编辑模式下,单击“模板”,清除现有模板,然后粘贴以下内容:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
SendMessage:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${AWS::StackName}-SendMessage
Description: !Sub
- Stack ${StackTagName} Environment ${EnvironmentTagName} Function ${ResourceName}
- ResourceName: SendMessage
CodeUri: src/SendMessage
Handler: index.handler
Runtime: nodejs12.x
MemorySize: 3008
Timeout: 30
Tracing: Active
Policies:
- AWSXrayWriteOnlyAccess
- SNSPublishMessagePolicy:
TopicName: !GetAtt XboxAlert.TopicName
Events:
EventRule:
Type: EventBridgeRule
Properties:
Pattern:
source:
- aws.codebuild
detail-type:
- CodeBuild Build State Change
detail:
build-status:
- SUCCEEDED
- FAILED
project-name:
- cypress-xbox-scraper
Metadata:
StackeryName: TriggerMessage
Environment:
Variables:
TOPIC_NAME: !GetAtt XboxAlert.TopicName
TOPIC_ARN: !Ref XboxAlert
CodeBuildIAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
Effect: Allow
Principal:
Service: codebuild.amazonaws.com
Action: sts:AssumeRole
RoleName: !Sub ${AWS::StackName}-CodeBuildIAMRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AdministratorAccess
CypressScraper:
Type: AWS::CodeBuild::Project
Properties:
Artifacts:
Type: NO_ARTIFACTS
Description: Cypress Xbox Scraper
Environment:
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/standard:2.0
Type: LINUX_CONTAINER
PrivilegedMode: true
Name: cypress-xbox-scraper
ServiceRole: !Ref CodeBuildIAMRole
Source:
BuildSpec: buildspec.yml
Location: https://github.com/<github-user>/<repo-name>.git
SourceIdentifier: BUILD_SCRIPTS_SRC
Type: GITHUB
Auth:
Type: OAUTH
CypressScraperTriggerIAMRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
Effect: Allow
Principal:
Service:
- events.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: TriggerCypressScraperCodeBuild
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- codebuild:StartBuild
- codebuild:BatchGetBuilds
Resource:
- !GetAtt CypressScraper.Arn
RoleName: !Sub ${AWS::StackName}-CypressScraperTriggerRole
TriggerScraper:
Type: AWS::Events::Rule
Properties:
ScheduleExpression: rate(1 hour)
State: ENABLED
RoleArn: !GetAtt CypressScraperTriggerIAMRole.Arn
Targets:
- Arn: !GetAtt CypressScraper.Arn
Id: cypress-xbox-scraper
RoleArn: !GetAtt CypressScraperTriggerIAMRole.Arn
XboxAlert:
Type: AWS::SNS::Topic
Properties:
TopicName: !Sub ${AWS::StackName}-XboxAlert
Parameters:
StackTagName:
Type: String
Description: Stack Name (injected by Stackery at deployment time)
EnvironmentTagName:
Type: String
Description: Environment Name (injected by Stackery at deployment time)
我们来详细分析一下。对于刚接触无服务器架构的朋友,这是一个AWS SAM模板。虽然使用 Stackery 通常可以避免编写模板文件,但仍有几点需要注意,并且其中一行需要您输入自己的数据。
我们先来看第 55-74 行:
CypressScraper:
Type: AWS::CodeBuild::Project
Properties:
Artifacts:
Type: NO_ARTIFACTS
Description: Cypress Xbox Scraper
Environment:
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/standard:2.0
Type: LINUX_CONTAINER
PrivilegedMode: true
Name: cypress-xbox-scraper
ServiceRole: !Ref CodeBuildIAMRole
Source:
BuildSpec: buildspec.yml
Location: https://github.com/<github-user>/<repo-name>.git
SourceIdentifier: BUILD_SCRIPTS_SRC
Type: GITHUB
Auth:
Type: OAUTH
这是一个 CodeBuild 项目,它将用于在 AWS 的某个神奇服务器环境中,以 Linux 容器的形式运行 Cypress。您需要将第 70 行替换为您刚刚创建的 Git 仓库。这也意味着您可能需要向 AWS 验证您的 Git 提供商,稍后我会详细介绍。
第 101 行允许您更改消息发送频率。点击此处了解更多关于 AWS 调度表达式的信息。
现在,如果您切换回可视化模式,您会发现一些资源已根据模板自动填充:
其中包括:
TriggerScraper:每小时触发一次 Cypress CodeBuild 作业的 CloudWatch 事件规则TriggerMessageSendMessage:当 CodeBuild 作业成功或失败时触发该函数的 EventBridge 规则SendMessage:用于在 Xbox 重新到货时向社交媒体发送消息的 Lambda 函数XboxAlert用于发送短信的 SNS 主题
您可以双击每个资源来查看其各自的设置。
瞧瞧:整个后端都搭建好了,而且你甚至都不用打开 AWS 控制台!
- 点击“提交...”按钮将其提交到您的 Git 仓库,然后点击堆栈名称下方的链接访问您的新仓库 URL,将堆栈克隆到本地,并在您喜欢的 VSCode(或其他文本编辑器,如果必须的话)中打开它。
开始编写代码!
如您所见,Stackery 为您的函数创建了一些目录,以及一个您可以部署的 AWS SAM 模板。谢谢 Stackery!
首先,我们需要添加 Cypress:
- 从仓库根目录运行
npm install cypress --save - 安装完成后,运行
./node_modules/.bin/cypress open。
Cypress 会创建一个包含大量示例代码的目录。您可以删除该目录并cypress/integration/examples重新创建cypress/integration/scraper.spec.js。以下是该目录中将包含的内容:
// xbox-stock-alert/cypress/integration/scraper.spec.js
describe('Xbox out-of-stock scraper', () => {
it('Checks to see if Xboxes are out of stock at Microsoft', () => {
cy.visit('https://www.xbox.com/en-us/configure/8WJ714N3RBTL', {
headers: {
"Accept-Encoding": "gzip, deflate",
"keepAlive": true
}
});
cy.get('[aria-label="Checkout bundle"]')
.should('be.disabled')
});
});
让我们来详细分析一下:
- Cypress 将访问一个特定的 URL——在本例中,它是 Xbox Series X 主机的产品页面。
- 添加这些头部信息后,页面就能正常加载,而不会出现可怕的ESOCKETTIMEDOUT 错误(我可是吃过亏才发现这一点的,所以你们就不用再经历一遍了!)
- Cypress 会查找包含“Checkout bundle”的元素
aria-label,并检查它是否被禁用。如果被禁用,则测试结束,并被视为成功。如果未被禁用,则测试失败(但我们都知道它已经尽力了)。
那么,为什么是特定的“结账套装”元素呢?嗯,如果你在浏览器中打开 Xbox 页面并检查它,你会发现它实际上是 Xbox 有货时才会启用的结账按钮:
让我们把这破事儿自动化吧!
好了,我们已经编写好了测试,并且设置了一个定时器,让它每小时运行一次。现在我们需要添加一个 CodeBuild 作业来实际运行这个测试。我们还需要在我们的SendMessage函数中添加代码,以便在测试失败时通知我们,这意味着“结账”按钮已启用,我们离拥有新 Xbox 又近了一步。
还记得你很久以前记下的那张柏树projectId唱片key吗?现在它们就派上用场了。
在根目录下创建一个名为 `.md.txt` 的新文件,并添加以下内容buildspec.yml并保存:
version: 0.2
phases:
install:
runtime-versions:
nodejs: 10
build:
commands:
- npm install && npm run cypress -- --headless --browser electron --record --key <your-record-key>
打开cypress.json并替换为以下内容,然后保存:
{
"baseUrl": "https://www.xbox.com/en-us/configure/8WJ714N3RBTL",
"defaultCommandTimeout": 30000,
"chromeWebSecurity": false,
"projectId": "<your-projectId>"
}
接下来,我们将添加测试失败时发送警报的功能代码。打开src/SendMessage/index.js并替换为以下内容:
// xbox-stock-alert/src/SendMessage/index.js
const AWS = require('aws-sdk');
const sns = new AWS.SNS({region: 'us-west-2'});
const message = 'Xbox alert! Click me now: https://www.xbox.com/en-us/configure/8WJ714N3RBTL';
const defaultMessage = 'No Xboxes available, try again later';
exports.handler = async (event) => {
// Log the event argument for debugging and for use in local development
console.log(JSON.stringify(event, undefined, 2));
// If the CodeBuild job was successful, that means Xboxes are not in stock and no message needs to be sent
if (event.detail['build-status'] === 'SUCCEEDED') {
console.log(defaultMessage)
return {
statusCode: 200,
body: defaultMessage
};
} else if (event.detail['build-status'] === 'FAILED') {
// If the CodeBuild job failed, that means Xboxes are back in stock!
console.log('Sending message: ', message);
// Create SNS parameters
const params = {
Message: message, /* required */
TopicArn: process.env.TOPIC_ARN,
MessageAttributes: {
'AWS.SNS.SMS.SMSType': {
DataType: 'String',
StringValue: 'Promotional'
},
'AWS.SNS.SMS.SenderID': {
DataType: 'String',
StringValue: 'XboxAlert'
},
},
};
try {
let data = await sns.publish(params).promise();
console.log('Message sent! Xbox purchase, commence!');
return {
statusCode: 200,
body: data
};
} catch (err) {
console.log('Sending failed', err);
throw err;
}
}
return {};
};
哦,对了,你最好把node_modules`and`添加package-lock.json到你的`<path>` 中.gitignore,除非你喜欢污染 Git 仓库。
是时候部署这辆狠角色了
请务必执行 `git add`、`git commit` 和 `git push` 命令来提交更改。部署时,AWS 需要访问您的 Git 提供商。如果您之前从未设置过访问令牌,请按照以下说明在您的账户中设置访问令牌。(这份文档对像我这样的新手也可能很有帮助。)
如果你像你这样既聪明又帅气的开发者一样,使用 Stackery 进行部署,那么你只需要在你的代码仓库根目录下运行以下命令:
stackery deploy
这需要几分钟时间,在此期间,你可以畅想一下当新的 Xbox 连接到你的 4K 电视上时会有多么棒。
完成了吗?好的!下一步:添加您的手机号码以接收短信提醒。
我可以要你的电话号码吗?
正如我上面提到的,你的堆栈中创建的资源之一是XboxAlert SNS 主题。它是在部署期间创建的,但目前它没有任何作用。让我们来改变这种情况。
- 打开 AWS 控制台,并导航至 SNS 控制面板。
- 在“主题”下,您应该会看到您刚刚创建的主题,名称类似于“主题名称”
xbox-stock-alert-<env>-XboxAlert。点击它的名称。 - 点击橙色的大“创建订阅”按钮
- 请按如下方式填写表格,包括您的手机号码,然后再次点击“创建订阅”:
如果您之前没有在社交网络上使用过您的手机号码,则需要验证您的手机号码,然后就可以开始使用了!
测试时间
现在您仍然在 AWS 环境中,应该可以打开 CodeBuild 控制台并看到其中新建了一个项目:
在设置好并置之后,您需要手动运行一次以确保一切正常。请选择您的项目并点击“开始构建”按钮。构建过程也需要一些时间,但您可以通过点击项目名称并选择最近一次的构建运行来查看 CloudWatch 日志。
没视频就等于没发生过
希望你的构建过程一切顺利(如果失败了,请联系我——我想我在构建过程中已经遇到过所有错误,或许可以帮上忙)。
但如何才能确定呢?你可以回到 Cypress.io 上的项目,看看最近几次运行的结果。如果一切顺利,你就能看到无头浏览器运行你的测试用例的视频了!
如果有一天测试失败了🤞,你会立即收到手机通知,告诉你Xbox就在那里等着你。祝你好运!
笔记
1这句话其实是我编的,但我猜锤子的发明者可能说过类似的话。2
这句话也是我编的,但这并不妨碍它的真实性。3
更好的方法是使用存储在AWS Systems Manager Parameter Store中的环境变量来存储记录键,但为了简洁起见,我的示例使用了硬编码。如果你要照搬我这个不太好的例子,请务必确保你的代码库是私有的🙏
后记
虽然可以扩展抓取程序规范以添加更多零售商,但我遇到了一些问题,例如沃尔玛的机器人检测器:
我没能成功运行这些程序,总是出错,但也许其他人运气更好,可以分享他们的解决方案:
// xbox-stock-alert/cypress/integration/scraper.spec.js
describe('Xbox out-of-stock scraper - more retailers', () => {
it('Checks to see if Xboxes are out of stock at GameStop', () => {
cy.visit('https://www.gamestop.com/accessories/xbox-series-x/products/xbox-series-x/11108371.html?condition=New', {
headers: {
"Accept-Encoding": "gzip, deflate",
"keepAlive": true
}
});
cy.get('span.delivery-out-of-stock')
cy.get('span.store-unavailable')
});
it('Checks to see if Xboxes are out of stock at Best Buy', () => {
cy.visit('https://www.bestbuy.com/site/microsoft-xbox-series-x-1tb-console-black/6428324.p?skuId=6428324', {
headers: {
"Accept-Encoding": "gzip, deflate",
"keepAlive": true
}
});
cy.get('[data-sku-id="6428324"]')
.should('be.disabled')
});
it('Checks to see if Xboxes are out of stock at Walmart', () => {
cy.visit('https://www.walmart.com/ip/Xbox-Series-X/443574645', {
headers: {
"Accept-Encoding": "gzip, deflate",
"keepAlive": true
}
});
cy.get('.spin-button-children')
.contains('Get in-stock alert');
});
it('Checks to see if Xboxes are out of stock at Costco', () => {
cy.visit('https://www.costco.com/xbox-series-x-1tb-console-with-additional-controller.product.100691493.html', {
headers: {
"Accept-Encoding": "gzip, deflate",
"keepAlive": true
},
pageLoadTimeout: 60000
});
cy.get('.oos-overlay')
});
});










