几天前,The Web Scraping Club 社区的一位成员询问了一些关于如何绕过 AWS WAF 保护以从 API 终端节点抓取数据的建议。
鉴于我们正在谈论抓取公共数据,我认为与您分享我研究过的解决方案可能会很有趣。
当然,以下技术必须用于合法目的,不得损害目标网站或其业务。
在深入研究解决方案之前,什么是 WAF,AWS WAF 的特点是什么?
什么是 WAF?
Web 应用程序防火墙 (WAF) 是一种专门的安全系统,旨在通过过滤和监控 Web 应用程序与 Internet 之间的 HTTP 流量来保护 Web 应用程序。它通过分析发送到 Web 应用程序的数据包和请求并根据预定义的安全规则过滤掉可能有害的请求来运行。WAF 的主要功能是防范常见的 Web 漏洞和漏洞,例如 SQL 注入、跨站点脚本 (XSS)、跨站点请求伪造 (CSRF) 和其他 OWASP 前 10 大威胁。
WAF 通常位于 Web 应用程序前面,充当客户端和服务器之间的中介。它检查传入和传出流量,允许合法流量通过,同时阻止恶意流量。此检查可以根据各种因素完成,例如 IP 地址、HTTP 标头、URL 模式和有效负载内容。
我们如何识别 AWS WAF?
虽然 Wappalyzer 无法识别这项技术,但您可以通过检查会话 cookie 轻松了解网站是否正在使用它。
如果您找到密钥 aws-waf-token,则网站正在使用 AWS WAF 是不言自明的。
根据我通过分析某些网站的行为所了解的情况,在浏览会话开始时,会向 AWS 服务器发送一个请求以验证您的浏览器配置。
正如您在有效负载中看到的那样,目前尚不清楚将发送到服务器的内容,并且需要一个逆向工程过程来了解后台发生的事情。
如果您已经关注此时事通讯一段时间,那么您已经知道我现在在说什么:这个过程很耗时并且需要不断更新,因此这是向网络爬虫销售商业解决方案的公司的任务。
如果我们的主要业务是销售数据,我们有两个选择:购买商业解决方案或使用我们拥有的工具发挥创意,使我们的抓取工具更接近人类。
事实上,与其对反机器人解决方案进行逆向工程,不如使用合法的硬件和软件堆栈模拟人类与网站交互是一种更有效的方法,能够持续更长的时间,因为它不应该受到反机器人发布的影响。
AWS WAF 的工作原理
正如我们在上一段中看到的,一旦我们访问网站,AWS WAF 就会在我们的浏览器后台引发挑战。如果配置看起来合法,我们会收到一个 cookie,证明我们已经通过了测试,并且我们可以在没有任何其他控制的情况下继续浏览,直到 cookie 弃用。
到期后,会抛出新的挑战,轮子会继续旋转。此测试的频率可能取决于网站配置,并且可能因情况而异。
正如我们几周前刚刚看到的 Bearer Token 一样,这是其他机器人保护措施的常见方法。
虽然这对网站来说是一种轻量级的方法,但由于没有持续监控访问者的活动,因此可以利用这种方法来编写一个高效的抓取工具,正如我们现在将在 Traveloka 的情况下看到的那样,Traveloka 是亚太地区的航班和酒店票价聚合器。
与往常一样,如果您想查看代码,可以访问 GitHub 存储库,可供付费读者使用。您可以在文件夹 53.AWS_WAF 中找到此示例
如果您是其中之一但无法访问它,请写信给我 pier@thewebscraping.club 以获取它。
检查 Traveloka 网站
Traveloka 的网站具有该行业的传统架构:主页、酒店或航线的搜索栏,以及选择路线和出发时间后,不同航空公司的报价列表。
为了加载这些数据,通常前端后面有一个 API,Traveloka 也不例外。
我们有一个 POST 请求,其中包含 payload 中的路由信息和一些其他信息。
curl 'https://www.traveloka.com/api/v2/flight/search/oneway' \
-H 'accept: */*' \
-H 'accept-language: en-US,en;q=0.9' \
-H 'content-type: application/json' \
-H 'cookie: selectedCurrency=EUR; currentCountry=IT; tv-repeat-visit=true; countryCode=IT; aws-waf-token=b6ac2c13-29d5-4f86-95a5-73903c784c47:DgoAhDUoz7YDAAAA:lkKEO0jAbax0/WqI1zMHelpnt/rI3wGD+t+66c+UWYsRD9lPdvjz+Z2cRCSFSeXTSA4zbzakIrab0/ckkvsPKH1dWoGYjUfDTLlsZVBcTTSJrH8KCkE9ZhdlDg5euQQzEmOu074HbFQ0GlbAc4VQsHmRk7Xe5XQvYtYz0Jc/u5grIqwbw4p01TEHKsAGEGVuRc8=; tvl=A3IvxWd/GJSwQafHmC3w6G8ICzVtxyfiYeyuSJlhufE6pzKOC7QVMHH+jpKIeDLow+AT8npt8+iEbJ1BLVsP6RiBKGEWVBZkEO1axIeYcyrnR8L6zqiyLXL0w/O6kFJLtEIhxFAMDSrD5WCsfwMHSrzME0HNmMOjNr0lQfzMUfpIaFVPwPCxQefTLSs9kReGBfi1NjCikXdEeJc6ZJJKY4sMbejHr0QQfqAFOCYNBWPh2luex9wS4XFCiVupQ6ddUB0WEfH/Fm8=~djAy; tvs=bPqvVC1YfJBQjiihg7CLqsjuzDqXHA/FddWAXUK1S2YIFIYikdiRXT+S9MUEPFWf9jwKwywLLRwG/RDWJcxWk8wgL1JyYgbN+V/RmnEGgVk16FMMqrwcGWzYU/R6IiudR24D8oM7l+aAnoMIbMxTsdLrxOUpUEDJryabfA14+t2UpzgCsa4J54MfO9eZUqAjj1N3d3ifKRvxTUX5egZK6DrLPO/cO0pLSaUvZ2PfEyQgVkqmM0/COREh02kmGCZ3OuH8m4Mca1L3SrSiWgRAdp9q3jRE0/7HhVU=~djAy; _dd_s=logs=1&id=65e1ce4a-ac32-45b2-bf1d-25b0eb10afb0&created=1717653197178&expire=1717654107146&rum=0' \
-H 'origin: https://www.traveloka.com' \
-H 'priority: u=1, i' \
-H 'referer: https://www.traveloka.com/en-en/flight/fullsearch?ap=DEL.BKK&dt=16-6-2024.NA&ps=1.0.0&sc=ECONOMY' \
-H 'sec-ch-ua: "Brave";v="125", "Chromium";v="125", "Not.A/Brand";v="24"' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'sec-ch-ua-platform: "macOS"' \
-H 'sec-fetch-dest: empty' \
-H 'sec-fetch-mode: cors' \
-H 'sec-fetch-site: same-origin' \
-H 'sec-gpc: 1' \
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36' \
-H 'x-domain: flight' \
-H 'x-route-prefix: en-en' \
--data-raw '{"fields":[],"clientInterface":"desktop","data":{"currency":"EUR","isReschedule":false,"locale":"en_EN","numSeats":{"numAdults":1,"numChildren":0,"numInfants":0},"seatPublishedClass":"ECONOMY","destinationAirportOrArea":"BKK","flexibleTicket":false,"flightDate":{"year":2024,"month":6,"day":16},"sourceAirportOrArea":"DEL","newResult":true,"seqNo":null,"searchId":"e55da74a-7ee0-4ede-8f34-1824f0d47d4c","visitId":"300d87cc-99fb-4d4f-9da1-ab767ca06ab5","utmId":null,"utmSource":null,"searchSpecRoutesTotal":1,"trackingContext":{"entrySource":""},"searchSpecRouteIndex":0,"journeyIndex":0}}'使用 Curl 和 API 端点,我推断出与我们的范围相关的 Cookie 是:
selectedCurrency (所选货币)
当前国家
电视重复访问
countryCode (国家代码)
aws-waf-令牌
电视
TVL
前四个可以设置为固定值,而后三个在我们开始浏览网站时分配给我们的浏览器会话。
出于测试目的,我们对请求使用相同的有效负载,因为在这种情况下更改航班的路线和日期并不重要。
第一次尝试:只用 Scrapy
让我们看看如果我们使用 Scrapy 获取数据,网站的行为如何,检查我们是否能够获得我们需要的 cookie。
在存储库的 Scrapy 文件夹中,您可以找到完整的抓取工具,我们基本上在其中为我们的请求使用一组合法的标头,加载主页,然后转到特定旅行路线的报价列表。
HEADER={
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"accept-language": "en-US,en;q=0.5",
"cache-control": "max-age=0",
"priority": "u=0, i",
"sec-ch-ua": "\"Chromium\";v=\"124\", \"Brave\";v=\"124\", \"Not-A.Brand\";v=\"99\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"sec-fetch-site": "none",
"sec-fetch-user": "?1",
"sec-gpc": "1",
"upgrade-insecure-requests": "1",
}
LOCATIONS = location_file.readlines()
def start_requests(self):
for i, url in enumerate(self.LOCATIONS):
yield Request(url, callback=self.get_flights, headers=self.HEADER, dont_filter=True)
def get_flights_page(self, response):
url='https://www.traveloka.com/en-en/flight/fullsearch?ap=DEL.BKK&dt=16-6-2024.NA&ps=1.0.0&sc=ECONOMY'
yield Request(url, callback=self.read_token, headers=self.HEADER, dont_filter=True)
def read_cookies(self, response):
print("End of scraper. No cookies get and cannot use the API")虽然我们没有因为获取这些页面的 HTML 而被阻止,但这非常没有用。在选件页面代码中,没有有关不同票价的信息,因为它们是从 API 调用响应动态加载的。
此外,我们没有获得任何可用于调用 API 的有趣 cookie,因此此爬虫无处可去。我们需要改变我们的方法。
第二次尝试:Scrapy + Playwright
要从 Traveloka 抓取旅行票价,我们需要获取 API 终端节点所需的 Cookie,尤其是 aws-waf-token,并在我们的 post 请求中使用它们。
既然这些 cookie 是在我们第一次浏览网站时分配的,为什么我们不使用 Playwright 来收集它们,然后在 Scrapy POST 调用中注入它们呢?
通过这种方式,我们可以使用一个 Headful 解决方案来发出一个请求,然后使用 Scrapy 及其处理并行性的能力来创建一个能够在几分钟内处理数百个请求的高效抓取器。
事实上,一旦我们获得 aws-waf-token,至少对于 Traveloka,这将在四天内有效,因此我们可能只使用一个令牌来满足我们的所有抓取需求。
但是我们如何将 Playwright 和 Scrapy 混合在同一个 spider 中呢?
嗯,这是scrapy-Playwright库的任务,你可以在仓库的目录53.AWS-WAF/Scrapy-Playwright 中找到完整的代码,只有付费读者才能使用。
使用 pip 安装 scrapy-Playwright 库很简单,就像它在 Scrapy spider 中的集成一样。
在 settings.py 文件中添加这些行 就足以在需要时在 scraper 中调用 Playwright。
DOWNLOAD_HANDLERS = {
"http": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
"https": "scrapy_playwright.handler.ScrapyPlaywrightDownloadHandler",
}
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
PLAYWRIGHT_BROWSER_TYPE = "firefox"
PLAYWRIGHT_LAUNCH_OPTIONS = {
"headless": False,
"timeout": 20 * 1000, # 20 seconds
}此外,修改 scraper 也非常简单,并且在文档中有很好的描述。
def start_requests(self):
for i, url in enumerate(self.LOCATIONS):
yield Request(url, callback=self.get_flights_page, headers=self.HEADER, dont_filter=True, meta={"playwright": True, "playwright_include_page": True,'playwright_page_methods': [PageMethod('wait_for_timeout', 5000)]})
def get_flights_page(self, response):
url='https://www.traveloka.com/en-en/flight/fullsearch?ap=DEL.BKK&dt=16-6-2024.NA&ps=1.0.0&sc=ECONOMY'
yield Request(url, callback=self.read_cookies, headers=self.HEADER, dont_filter=True, meta={"playwright": True, "playwright_include_page": True,'playwright_page_methods': [PageMethod('wait_for_timeout', 5000)]})
我们在 Scrapy 请求中的 meta 字典中添加参数
"playwright": True, 使用 Playwright 而不是传统的 Scrapy 请求。
这将打开浏览器的窗口(我们在 settings.py 文件中选择的浏览器),并浏览器到所选 URL。
此外,我们还提供了选项
"playwright_include_page": True,'playwright_page_methods': [PageMethod('wait_for_timeout', 5000)],以便响应在 meta 参数中包含 Playwright Page 对象。
事实上,在接下来的步骤中,我们将使用此语法从响应中读取 cookie,以便我们可以在下一个 Scrapy 请求中使用它们。
async def read_cookies(self, response):
page = response.meta["playwright_page"]
cookies = await page.context.cookies()
#print(cookies)
await page.close()
aws_waf_token=''
tvl=''
tvs=''
for cookie in cookies:
if cookie['name']== 'aws-waf-token':
aws_waf_token=cookie['value']
if cookie['name']== 'tvl':
tvl=cookie['value']
if cookie['name']== 'tvs':
tvs=cookie['value']您可以在存储库中找到完整的代码,如果您无法访问它,请在 pier@thewebscraping.club 写信给我,因为我需要手动授予您访问权限。
最后的考虑
用于 AWS WAF 的这种技术很有趣,可用于类似的使用案例,例如受 Akamai 保护的网站,在通过初始质询后,我们会得到一个 Cookie,该 Cookie 为我们在整个网站上开了一段时间的绿灯。
爬虫的初始 Playwright 部分是最重要的部分,因为反机器人会测试这些请求。Scrapy-Playwright 允许我们添加代理或通过 CDP 连接到反检测浏览器,以使第一步与真实步骤更加难以区分。
一旦我们得到 cookie,我们就可以使用 Scrapy 来扩展我们的数据收集,直到它们的过期日期。这个spider的一个改进可能是错误处理程序:当Scrapy开始返回错误时,我们可以通过再次加载Playwright页面并获取新的cookie来重新启动整个循环。
我希望这篇文章,即使很简单,也能帮助您完成当前或下一个抓取项目,我很高兴听到您的反馈。





Comments
Post a Comment