首先爬虫部分,为了实现最大程度上触发更多的请求、事件、流量,我们有且只有唯一的选择为Chrome Headless。
这里我选择了selenium来操作Chrome WebDriver。值得注意的是几个比较重要的配置
。
self.chrome_options.add_argument('--headless')
self.chrome_options.add_argument('--disable-gpu')
self.chrome_options.add_argument('--no-sandbox')
self.chrome_options.add_argument('--disable-images')
self.chrome_options.add_argument('--ignore-certificate-errors')
self.chrome_options.add_argument('--allow-running-insecure-content')
self.chrome_options.add_argument('blink-settings=imagesEnabled=false')
self.chrome_options.add_argument('--omnibox-popup-count="5"')
self.chrome_options.add_argument("--disable-popup-blocking")
self.chrome_options.add_argument("--disable-web-security")
self.chrome_options.add_argument("--disk-cache-size=1000")
除了设置headless模式以外,还关闭了一些无意义的设置。
if os.name == 'nt':
chrome_downloadfile_path = "./tmp"
else:
chrome_downloadfile_path = '/dev/null'
prefs = {
'download.prompt_for_download': True,
'profile.default_content_settings.popups': 0,
'download.default_directory': chrome_downloadfile_path
}
设置好文件下载的目录,如果没设置的话会自动下载大量的文件在当前文件夹。
desired_capabilities = self.chrome_options.to_capabilities()
if IS_OPEN_CHROME_PROXY:
logger.info("[Chrome Headless] Proxy {} init".format(CHROME_PROXY))
desired_capabilities['acceptSslCerts'] = True
desired_capabilities['acceptInsecureCerts'] = True
desired_capabilities['proxy'] = {
"httpProxy": CHROME_PROXY,
"ftpProxy": CHROME_PROXY,
"sslProxy": CHROME_PROXY,
"noProxy": None,
"proxyType": "MANUAL",
"class": "org.openqa.selenium.Proxy",
"autodetect": False,
}
通过org.openqa.selenium.Proxy来设置浏览器代理,算是比较稳定的方式。
self.driver.set_page_load_timeout(15)
self.driver.set_script_timeout
(5)
这两个
配置可以设置好页面加载的超时时间,在大量的扫描任务中,这也是必要的。
你必须在访问页面之后才可以设置cookie,且cookie只能设置当前域,一旦涉及到跳转,这种cookie设置方式就不会生效。
self.origin_url = url
self.driver.implicitly_wait(5)
self.driver.get(url)
if cookies:
self.add_cookie(cookies)
self.driver.implicitly_wait(10)
self.driver.get(url)
在配置好chrome headless之后,为了模拟人类的使用,我抛弃了传统爬虫常用的拦截、hook等获取请求并记录的方式,转而将重心放在模拟点击以及智能填充上。
links = self.driver.find_elements_by_xpath('//a')
link = links[i]
href = link.get_attribute('href')
self.driver.execute_script(
"atags = document.getElementsByTagName('a');for(i=0;i<=atags.length;i++) { if(atags[i]){atags[i].setAttribute('target', '')}}")
if link.is_displayed() and link.is_enabled():
link.click()
self.check_back()
当获取到对应标签时,首先将对应的标签属性taget置空(不打开新的标签页),然后模拟点击按钮,之后检查是否发生跳转,并返回原页面。
同样的逻辑被复用在了button,input@type=submit和拥有onclick事件标签上,值得注意的是。button这样的标签还加入模拟鼠标移动的操作。
if submit_button.is_displayed() and submit_button.is_enabled():
action = ActionChains(self.driver)
action.move_to_element(submit_button).perform()
submit_button.click()
智能表单填充
这里我把智能表单填充分为两部分,首先我们需要判断当前页面是否存在一个登录框,这意
味着当前页面并没有登录(如果登录状态,一般登录框会消失)。
form -> 文本中出现login、登录、sign等
button -> outerHTML出现login、user、pass等
input -> outerHTML出现登录、注册等关键词
a -> 文本出现login、登录、sign等
当前页面满足上述任一条件时,则会记录下来到相应的位置(后续会提到)。
然后会尝试填充页面的所有框框。
inputs = self.driver.find_elements_by_xpath("//input")
self.driver.execute_script(
"itags = document.getElementsByTagName('input');for(i=0;i<=itags.length;i++) { if(itags[i]){itags[i].removeAttribute('style')}}")
input_lens = len(inputs)
if not inputs:
return
for i in range(input_lens):
try:
input = inputs[i]
# 移动鼠标
# 如果标签没有隐藏,那么移动鼠标
if input.is_enabled() and input.is_displayed():
action = ActionChains(self.driver)
action.move_to_element(input).perform()
首先去掉表单框上的所有CSS,并将鼠标放置到对应的位置。
紧接着通过判断表单的key值,满足条件的对应填入部分预设的用户名,密码,邮箱,地址,手机号,并勾选所有的单选多选框,其余输入框则生成随机字符串填入。
到目前为止,我们至少触发了属于页面中大量的请求,接下来我们就遇到了另一个问题,如何对流量去重?
除了尽可能的触发请求以外,爬虫也并不是单一执行流程的,每个爬虫需要不断地从某个主控(RabbitMQ)获取新的目标,而爬虫也需要不断地返回目标,但我们就需要去重逻辑来完成新目标的处理,这样可以最大限度的减少无意义的请求。
这里我使用了一套比较简单的url泛化逻辑来做去重。首先我将获取的返回和历史记录中相同域的url提取出来。
BLACK_DOMAIN_NAME_LIST = ['docs', 'image', 'static', 'blogs']
首先,如果域名中存在上面4个黑名单词语,则只接受200条不重复请求。
然后将,所有的路径泛化,
按照/做分割,用A来代表纯数字段,用B来代表字母混合段。
https://lorexxar.cn/2020/10/30/whitebox-2/
就会被泛化为AAAB
如果历史记录已经存在flag为AAAB的链接,则会进入更深层次的判断。
紧接着会提取比较的两个链接的B段内容。
BLACK_PATH_NAME = ['static', 'upload', 'docs', 'js', 'css', 'font', 'image']
如果满足,那么直接限制该路径下只收录100个链接,如果不满足,那么会提出两个url的B段进行比较。也就是
whitebox-2
,如果不同,则判定为拥有1个不同点。
且如果当前B段为路径的最后一部分,举例子为:
https://lorexxar.cn/2020/10/30/whitebox-2/a.php
这里的a.php就是路径的最后一部分,如果
两个链接的最后一部分不为静态资源,则会被直接认定为不同请求。
如果最后一部分相同,且不同点不超过1个,那么会进入参数判断。
这里我们直接简单粗暴的获取所有请求的key,
如果两个请求都拥有相同的参数列表,则两个链接为不同请求。
(会剔除没有value的参数,如?20210127这类时间戳)
直接经过所有的判断,该链接才会被加入到新的目标列表当中,等待下一次被塞入任务队列中。