记录一下写的一个简单爬虫
简介 selenium是基于浏览器DevTool模拟用户态行为的爬虫框架,与传统爬虫相比selenium最大的优点就是可以通过模拟行为绕过大部分的反爬,同时因为其本身基于浏览器,在对抗加密方面有天然的优势
常用API webDriver.Chrome.Options() : Option 返回一个 option 对象,用于在之后为浏览器提供选项参数,最常见的用法如下
1 2 options = webdriver.ChromeOptions() options.add_argument("disable-blink-features=AutomationControlled" )
这段代码为option添加了一个参数将浏览器的 webdriver 属性置为false,该属性常用于检测浏览器是否处于调试模式,常见的反爬会检测该属性,该属性为true则会触发反爬 此外还可以添加包括但不限于下列的属性,但用处都不大
1 2 3 --disable-gpu --headless --windows-size=1920 ,1080
webdriver.Chrome(options : Option ) : WebDriver 创建一个浏览器对象,所有与浏览器的交互都会通过这个对象进行
1 driver = webdriver.Chrome(options=options)
driver.get(url : str ) : void 打开一个页面
driver.find_element(by: str , value : str ) : WebElement 在webdriver目前聚焦的页面中寻找对应html元素,并返回一个可以对找到的元素进行交互的对象, by 是 selenium.webdriver.common.by 中的枚举类, value 是搜索对应的值或表达式,这里只介绍 By.CSS_SELECTOR 和 By.LINK_TEXT ,个人认为算是最通用的方法。 这个方法只返回找到的第一个元素,且找不到则抛出错误,另一个类似的方法是 driver.find_elements 返回所有找到的元素,如果找不到就返回空列表
By.CSS_SELECTOR 利用CSS选择器查找元素,value 应为一个CSS选择器表达式 下面是一些示例
1 2 3 4 5 6 7 8 9 10 11 12 13 value = "div" value = ".targetClass" value = "#ID" value = "[attribute]" value = '[attribute="value"]' value = "div.targetClass#ID" value = "div#fatherDiv div#sonDiv" value = "div#faterDiv > div#directSonDiv" value = ":not()"
By.LINK_TEXT 筛选文本为 value 的<a>元素即超链接元素,执行严格匹配,另一个类似的方法是 By.PARTIAL_LINK_TEXT ,只需提供的 value 是目标的子串就行,此方法用于快捷定位特定超链接对应的元素,方便爬虫在页面间进行跳转
1 Page = driver.find_element(by=By.LINK_TEXT, value="下一页" )
ActionChains(driver : WebDriver ) : ActionChains 这是selenium提供的动作链类,用于模拟用户的操作输入
1 2 3 4 5 6 7 actions = ActionChains(driver) actions.key_down(Keys.CONTROL).click( Page).key_up(Keys.CONTROL).perform()
driver.switch_to.window(window_name : str ) : void 将浏览器的焦点切换至另一个页面,其中 window_name 可以通过 driver.window_handles 获取,这是webdriver类的一个列表成员,储存了当前浏览器所有窗口的 window_name
1 2 driver.switch_to.window(driver.window_handles[-1 ])
driver.close() : void 当一个页面的数据爬完了可能要关闭该页面,用close方法就能把当前页面关掉,但是注意driver并 不会自动切换聚焦到新窗口 ,所以close后要调用switch_to.window把焦点转移到新窗口
driver.quit() : void 直接关闭浏览器
项目实战 项目地址 => 基于selenium框架爬取avd.aliyun.com上的漏洞报告
阿里云的漏洞网站用了很恶心的加密算法,把本机的时间戳套了一堆加密做成token放进payload里做验证,如果验证不通过就无法进入漏洞库和漏洞报告,我们直接上selenium,只要我们全程模拟用户态行为就不用管加密,这点和逆向工程中的动态调试非常像,本质上是利用了应用自加密/自解密的性质
下面直接上代码
1 2 3 4 5 6 7 8 def init_driver (url: str ): options = webdriver.ChromeOptions() options.add_argument("disable-blink-features=AutomationControlled" ) driver = webdriver.Chrome(options=options) driver.get(url) return driver
他的加密算法会在加载时就读取 webdriver 标记的值,并且这个参数会被用于加密过程,如果是undefined或者true加密跑出来就是错的,而且非常恶心的是他并不直接从 navigator.webdriver 实时读取,而是加载时检测到 webdriver 不为false就让加密胡乱输出,所以我们加一个参数在网页加载前就把 webdriver 标记设置成false
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 def runCrawler (catalog: str , number: int = 0 , debug: bool = 0 , sleepSecond: float = 1 ): ''' sleepSecond为每跳转一个页面的等待时长,过短可能会触发限流或反爬\n 如果cve的命名不符合windows文件命名规范则会转换为url命名输出\n catalog为要爬取的漏洞库的按钮文本{"CVE 漏洞库","非CVE漏洞库","高危漏洞"}\n number为爬取的记录数量,若不指定则在调用函数时提示输入 ''' driver = init_driver("https://avd.aliyun.com/" ) Page = driver.find_element(by=By.LINK_TEXT, value=catalog) if debug: print ("in " +driver.title+" at " +driver.current_url) actions = ActionChains(driver) actions.key_down(Keys.CONTROL).click( Page).key_up(Keys.CONTROL).perform() sleep(sleepSecond*1.5 ) if debug: print ("sleep finished" ) driver.switch_to.window(driver.window_handles[-1 ]) links = driver.find_elements(By.TAG_NAME, "a" ) if debug: print ("in " +driver.title+" at " +driver.current_url) total = driver.find_element( by=By.CSS_SELECTOR, value="div.py-3.bg-light > div.container.vuln-list-container > div.py-3 > div.d-flex.justify-content-between.align-items-center > span.text-muted" ).text if debug: print (total[total.find("总计 " )+3 : total.find(" 条记录" )]) total = int (total[total.find("总计 " )+3 : total.find(" 条记录" )]) print ("正在爬取 {}" .format (catalog)) toDo = number if toDo == 0 : toDo = int (input ("输入要爬取的记录条数,范围为 <={}\n" .format (total))) hasNextPage = 1 cnt = 0 while hasNextPage and cnt < toDo: if debug: print ("in" +driver.current_url) for link in links: if "detail" in link.get_attribute("href" ): link.click() sleep(sleepSecond) driver.switch_to.window( driver.window_handles[-1 ]) button = driver.find_elements( by=By.CSS_SELECTOR, value=".btn.btn-link.text-muted" ) if len (button): button[0 ].click() genRawDoc(driver, debug) cnt += 1 print ("进度 {} / {}" .format (cnt, toDo)) if debug: print ("in " +driver.title+" at " +driver.current_url) input ("continue" ) driver.close() driver.switch_to.window(driver.window_handles[-1 ]) if debug: print ("in " +driver.title+" at " +driver.current_url) if cnt == toDo: break nextPageButton = driver.find_elements( by=By.CSS_SELECTOR, value=".px-3.btn.btn-sm.btn-outline-secondary.btn-bd-primary:not(.disabled)" ) if debug: print (len (nextPageButton)) hasNextPage = 0 for button in nextPageButton: if "下一页" in button.text: actions.key_down(Keys.CONTROL).click( button).key_up(Keys.CONTROL).perform() sleep(sleepSecond) driver.close() driver.switch_to.window(driver.window_handles[-1 ]) hasNextPage = 1 break driver.quit()
这段非常长,因为爬虫定位元素基本靠手工定位,有大量重复代码,我们看关键的部分
1 2 3 4 5 driver = init_driver("https://avd.aliyun.com/" ) Page = driver.find_element(by=By.LINK_TEXT, value=catalog) actions = ActionChains(driver) actions.key_down(Keys.CONTROL).click(Page).key_up(Keys.CONTROL).perform() sleep(sleepSecond*1.5 )
这里我们打开网站,然后网站首页有三个链接分别跳进不同分类的漏洞库,我们直接用文本定位然后在新页面打开,并且等待加载,这里不直接点击在原地跳转的原因是不知道为什么原地跳转会导致加密算法输出不正确,然后等待加载也很重要,如果不等页面完全加载完就跳转也会让加密算法输出不正确,尚不清楚原因
1 2 driver.switch_to.window(driver.window_handles[-1 ]) links = driver.find_elements(By.TAG_NAME, "a" )
这段就是把这个页面里所有的超链接元素都抓下来
1 2 3 total = driver.find_element( by=By.CSS_SELECTOR, value="div.py-3.bg-light > div.container.vuln-list-container > div.py-3 > div.d-flex.justify-content-between.align-items-center > span.text-muted" ).text total = int (total[total.find("总计 " )+3 : total.find(" 条记录" )])
这段是找下总共有多少条记录,不是很重要
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 while hasNextPage and cnt < toDo: nextPageButton = driver.find_elements( by=By.CSS_SELECTOR, value=".px-3.btn.btn-sm.btn-outline-secondary.btn-bd-primary:not(.disabled)" ) if debug: print (len (nextPageButton)) hasNextPage = 0 for button in nextPageButton: if "下一页" in button.text: actions.key_down(Keys.CONTROL).click( button).key_up(Keys.CONTROL).perform() sleep(sleepSecond) driver.close() driver.switch_to.window(driver.window_handles[-1 ]) hasNextPage = 1 break
因为记录有很多页,我们要一页一页爬,先找到翻页按钮,然后发现能按和不能按的按钮有一个.disable类的区别,我们就一直翻直到”下一页”变成disable为止 然后每次点开一个新的就把老的关了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 for link in links: if "detail" in link.get_attribute("href" ): link.click() sleep(sleepSecond) driver.switch_to.window( driver.window_handles[-1 ]) button = driver.find_elements( by=By.CSS_SELECTOR, value=".btn.btn-link.text-muted" ) if len (button): button[0 ].click() genRawDoc(driver, debug) cnt += 1 print ("进度 {} / {}" .format (cnt, toDo)) if debug: print ("in " +driver.title+" at " +driver.current_url) input ("continue" ) driver.close() driver.switch_to.window(driver.window_handles[-1 ]) if debug: print ("in " +driver.title+" at " +driver.current_url) if cnt == toDo: break
页面里有一堆超链接,我们肯定不能每个都跳进去,发现漏洞详情的超链接里有detail关键字,我们就只找含这个关键字的跳,也是点开,转移焦点,爬数据,关闭,把焦点切回来,同样注意每次等待一会,可以防止访问量过大导致限流或者直接被封ip 然后跳进去后发现参考链接如果数量过多会只显示部分,要先点一下 .btn.btn-link.text-muted 的按钮把所有东西都展开 genRawDoc里的内容就没什么营养了,目前已经进入具体报告界面,直接定位对应内容,保存下来写入文件就行