本文的工程目的是使用ruby編寫一個腳本文件,實現(xiàn)對網(wǎng)頁中第三方廣告的檢測和統(tǒng)計哄尔。
項目源代碼:https://github.com/vito0705/selenium_vito
本文主要內(nèi)容
- 一.項目分析
- 項目目的
- 項目要求
- 項目解決思路
- 二.環(huán)境配置
- Linux下環(huán)境配置
- Windows下環(huán)境配置
- 三.程序編寫
- 項目設(shè)計思路
- 代碼實現(xiàn)
- (一)加載庫文件
- (二)初始化部分
- (三)網(wǎng)頁檢測部分
- (四)代碼執(zhí)行部分
- 四.腳本使用
- 五.總結(jié)
一.項目分析
項目目的
對頁面中的第三方廣告進行檢測女器,找出其中隱藏的廣告網(wǎng)頁并將數(shù)據(jù)記錄下來。
項目要求
- 檢測所有廣告及廣告的域并記錄下來
- 統(tǒng)計所有廣告的數(shù)目及其中隱藏廣告的數(shù)目
- 以表格形式保存數(shù)據(jù)
項目解決思路
- 第三方廣告都在網(wǎng)頁中的
iframe
標(biāo)簽中,需要從iframe
標(biāo)簽中獲取所需的數(shù)據(jù) - 根據(jù)需要重贺,我們選擇
selenium
作為web自動化測試工具 - 數(shù)據(jù)需要保存在表格中,我們選擇
spreadsheet
這個gem來實現(xiàn)相關(guān)功能
二.環(huán)境配置
Linux和windows下均可以使用這個腳本回懦,但對于環(huán)境配置略有不同气笙。
Linux下環(huán)境配置
1.安裝ruby
可以參考這篇文章中使用rvm管理ruby的方式安裝,要求ruby版本大于等于2.0怯晕,具體安裝不作更多說明潜圃。
2.安裝ruby版本的selenium
在terminal
中執(zhí)行:
gem install selenium-webdriver
3.安裝spreadsheet:
gem install spreadsheet
4.安裝selenium瀏覽器驅(qū)動driver
- Chrome
- 版本對應(yīng):ChromeDriver與Chrome版本的對應(yīng)關(guān)系
- driver下載:ChromeDriver - WebDriver for Chrome
- Firefox
- driver下載及版本對應(yīng):https://github.com/mozilla/geckodriver/releases
根據(jù)自己的瀏覽器版本,選擇對應(yīng)的selenium瀏覽器驅(qū)動版本driver進行下載解壓舟茶,將下載解壓好的driver文件移動到/usr/bin/
文件夾下即可谭期。
以上四步,是linux下運行程序必要的環(huán)境配置吧凉,務(wù)必保證每一步的正確安裝隧出。
Windows下環(huán)境配置
windows下的環(huán)境配置與Linux下略有不同,但思路是相通的阀捅。
1.安裝ruby
按照這篇文章《Ruby 安裝 - Windows》安裝ruby即可胀瞪,記得勾選Add Ruby executables to your PATH
這一項。同樣饲鄙,要求ruby版本大于等于2.0凄诞。
2.安裝ruby版本的selenium
在cmd
中執(zhí)行:
gem install selenium-webdriver
3.安裝spreadsheet:
gem install spreadsheet
4.安裝selenium瀏覽器驅(qū)動driver
- Chrome
- 版本對應(yīng):ChromeDriver與Chrome版本的對應(yīng)關(guān)系
- driver下載:ChromeDriver - WebDriver for Chrome
- Firefox
- driver下載及版本對應(yīng):https://github.com/mozilla/geckodriver/releases
根據(jù)自己的瀏覽器版本,選擇對應(yīng)的selenium瀏覽器驅(qū)動版本driver進行下載解壓忍级,將下載解壓好的driver文件放在對應(yīng)的瀏覽器安裝目錄下幔摸,之后需要對Windows環(huán)境變量進行配置。
Windows下需要在系統(tǒng)變量的path變量中添加exe文件的位置颤练,配置環(huán)境變量可參考這篇文章:Win7怎樣添加環(huán)境變量既忆,注意路徑中不要有中文。
同樣嗦玖,這四步也是Windows下必備的環(huán)境配置患雇。但在自己的測試過程中,由于一些安全問題宇挫,Windows下的chrome始終沒有調(diào)通苛吱,但Firefox是可以使用的。
三.程序編寫
項目設(shè)計思路
- 為了能使腳本檢測大量網(wǎng)站器瘪,我們使用三個文件翠储,一個txt文件绘雁,一個xls表格文件和包含所有邏輯功能的ruby文件。
- weburl.txt:在文件中援所,每個網(wǎng)址占一行庐舟,ruby文件會依次按行讀取此文件中的網(wǎng)址進行檢測
- ad_file.xls:用于保存數(shù)據(jù),最終的數(shù)據(jù)會寫入這個文件
- detection_ad.rb:所有的數(shù)據(jù)邏輯處理均包含在這個文件中住拭,負(fù)責(zé)檢測頁面中的第三方廣告挪略。
- 第三方廣告都在
iframe
標(biāo)簽中,我們的目的是找到這些iframe
標(biāo)簽中的src
滔岳,即就是第三方廣告的網(wǎng)址杠娱。因此我們可以將思路轉(zhuǎn)變?yōu)椋菏紫韧ㄟ^selenium獲取網(wǎng)頁的源代碼,之后通過ruby正則表達(dá)式來實現(xiàn)對關(guān)鍵信息的提取谱煤。 - 對于selenium和spreadsheet兩個gem的使用摊求,我們不作過多解釋,可以參考以下兩篇文章刘离,給出了兩個gem的基本使用方法睹簇。
代碼實現(xiàn)
代碼內(nèi)容我們分成將四部分來分別說明。
(一)加載庫文件
require 'rubygems'
require 'selenium-webdriver'
require 'spreadsheet'
(二)初始化部分
# 存放網(wǎng)址的文件
web_file = "weburl.txt"
# 創(chuàng)建excel表格實例
Spreadsheet.client_encoding = "UTF-8"
excel_fil = Spreadsheet::Workbook.new
sheet = excel_fil.create_worksheet :name => "ads_show"
# 創(chuàng)建瀏覽器driver實例
# driver = Selenium::WebDriver.for :chrome
driver = Selenium::WebDriver.for :firefox
# 創(chuàng)建三個全局變量
# web_num:excel表單中的行數(shù)
# all_ads_num:所有網(wǎng)頁的廣告總數(shù)
# hide_ads_num:所有網(wǎng)頁的隱藏廣告總數(shù)
$web_num = 1
$all_ads_num = 0
$hide_ads_num = 0
(三)網(wǎng)頁檢測部分
這部分的功能是檢測一個網(wǎng)頁中的所有第三方廣告寥闪,找到廣告的域并統(tǒng)計廣告的數(shù)量,進一步需要分離出頁面中隱藏的第三方廣告磨淌。
我們將這部分定義為一個方法:search_ads(driver, web_url_para, sheet)
疲憋,這個方法要求三個參數(shù):
- driver:已經(jīng)創(chuàng)建的瀏覽器driver實例,如
driver = Selenium::WebDriver.for :firefox
- web_url_para:待檢測網(wǎng)頁網(wǎng)址url
- sheet:已經(jīng)創(chuàng)建的excel表單實例梁只,如
sheet = excel_fil.create_worksheet :name => "ads_show"
接下來會從多個模塊來介紹這一部分內(nèi)容缚柳。
功能塊一
web_url = web_url_para
#--------------------------------------------------------
#web_url_domain:the domian of the web page
#--------------------------------------------------------
web_url_domain_raw = web_url.match(/https?\:\/\/(.*?)\/.*?/)
web_url_domain = web_url_domain_raw[1]
這部分使用正則匹配獲得待檢測網(wǎng)址的域,有兩個重要的點需要說明搪锣。
1.不同的域
所謂第三方秋忙,指的是在iframe
中嵌入的網(wǎng)頁的域與當(dāng)前網(wǎng)頁的域不同。那么什么是域呢构舟?在我之前介紹跨域解決方案rack-cors文章里灰追,舉了這樣一個例子:
那么什么是同源?我們知道狗超,URL由協(xié)議弹澎、域名、端口和路徑組成努咐,如果兩個URL的協(xié)議苦蒿、域名和端口相同,則表示他們同源渗稍。
我們用一個例子來說明:
URL: http://www.example.com:8080/script/jquery.js
在這個url中佩迟,各個字段分別代表的含義:
http://——協(xié)議
www——子域名
example.com——主域名
8080——端口號
script/jquery.js——請求的地址
當(dāng)協(xié)議团滥、子域名、主域名报强、端口號中任意一各不相同時灸姊,都算不同的“域”。不同的域之間相互請求資源躺涝,就叫跨域厨钻。
因此,需要獲得當(dāng)前網(wǎng)頁的域坚嗜,來和iframe
中的網(wǎng)址作對比夯膀,來判斷是否屬于第三方。
2.MatchData對象的分組捕獲
這里不對ruby中的正則表達(dá)式的語法進行詳述苍蔬,僅對其MatchData對象中的分組捕獲相關(guān)的幾點做簡單的說明诱建。
- match方法
- 可以雙向使用match方法,即正則表達(dá)式和字符串對象均可以響應(yīng)match方法碟绑。match方法會將字符串參數(shù)轉(zhuǎn)換為正則表達(dá)式
- match與
=~
的區(qū)別:正則表達(dá)式匹配后返回值不同俺猿,=~
返回字符串匹配中匹配的開始位置的數(shù)字索引,而match則返回MatchData實例:2.2.7 :017 > "The alphabet starts with abc" =~ /abc/ => 25 2.2.7 :018 > /abc/.match("The alphabet starts with abc") => #<MatchData "abc">
- MatchData對象
- 當(dāng)正則表達(dá)式通過match方法匹配時格仲,返回一個MatchData對象;當(dāng)正則表達(dá)式不匹配時凯肋,返回
nil
2.2.7 :019 > /abc/.match("abcd") => #<MatchData "abc"> 2.2.7 :020 > /abc/.match("bcd") => nil
- 分組捕獲
- 正則表達(dá)式通過圓括號指定捕獲(capture)侮东。當(dāng)一個字符串和模式之間進行正則匹配測試時圈盔,通常是想使用字符串,或者更常見的是用字符串的一部分完成一些操作悄雅。捕獲表示法讓用戶可以從能夠匹配特殊子模式的字符串中,抽取和保存字符子串众眨。
- 從MatchData對象中得到捕獲結(jié)果的一個方式是直接通過數(shù)組的方式索引對象:
0
索引會返回匹配的整個字符串;從1
開始往后容诬,n
的索引會基于從左邊的括號開始計數(shù)围辙,返回第n
個捕獲結(jié)果放案。關(guān)于“從左開始計數(shù)圓括號”的周期性,用一個例子來說明:
可以肯定的是掸冤,上式中,從左邊開始計數(shù)的成對圓括號之間匹配的結(jié)果铅匹,與結(jié)果嚴(yán)格對應(yīng)饺藤。a=/((a)((b)c)(d)?)/.match("abce") => #<MatchData "abc" 1:"abc" 2:"a" 3:"bc" 4:"b" 5:nil> a[0] => "abc" a[1] => "abc" a[2] => "a" a[3] => "bc" a[4] => "b" a[5] => nil (不匹配) a[6] => nil (超出范圍) a[-2] => "b"
- 當(dāng)正則表達(dá)式通過match方法匹配時格仲,返回一個MatchData對象;當(dāng)正則表達(dá)式不匹配時凯肋,返回
功能塊二
driver.get web_url
sleep 3
#--------------------------------------------------------
#get <iframe ...>...<\iframe>
#--------------------------------------------------------
html_source = driver.page_source
match_iframe = html_source.scan(/(<\s*iframe\s.*?>.*?<\s*\/\s*iframe\s*>)/)
這部分功能是訪問目標(biāo)網(wǎng)頁涕俗,獲取網(wǎng)頁源代碼,并獲得源代碼中所有的iframe
標(biāo)簽中的數(shù)據(jù)萌抵。
功能塊三
#--------------------------------------------------------
#select the third party hide ads url from iframe.src
#iframe_src_hide:hide ad url
#ad_hide_num: number
#--------------------------------------------------------
iframe_src_hide_raw = match_iframe.map do |ifr|
if (src_match = ifr[0].to_s.match(/(<\s*iframe\s.*?(src=\"(.*?)\".*?>))/) )
src_matched_hide = src_match[1].gsub(/\&\;/,"&")
hide_condition_1 = src_matched_hide.match(/.*?\swidth\s*\=\s*\"\s*0\s*px\s*\"\s.*?height\s*=\s*\"\s*0\s*px\s*\".*/)
hide_condition_2 = src_matched_hide.match(/.*?\sheight\s*\=\s*\"\s*0\s*px\s*\"\s.*?width\s*=\s*\"\s*0\s*px\s*\".*/)
hide_condition_3 = src_matched_hide.match(/.*?style\s*=\s*\".*?width\s*:\s*0\s*px\s*;.*?height\s*:\s*0\s*px.*?\"/)
hide_condition_4 = src_matched_hide.match(/.*?style\s*=\s*\".*?height\s*:\s*0\s*px\s*;.*?width\s*:\s*0\s*px.*?\"/)
hide_condition_5 = src_matched_hide.match(/.*?\sdisplay\s*=\s*\"\s*none\s*\"\s*/)
hide_condition_6 = src_matched_hide.match(/.*?style\s*=\s*\".*?display\s*:\s*none\s*.*?\"/)
if (hide_condition_1 || hide_condition_2 || hide_condition_3 || hide_condition_4 || hide_condition_5 || hide_condition_6)
# alert("123");
src_matched = src_match[3].gsub(/\&\;/,"&")
src_matched = src_matched.match(/https?\:\/\/(.*)\/.*/)
if src_matched
domain_judge_raw = src_matched[0].to_s.match(/https?\:\/\/(.*?)\/.*?/)
domain_judge = domain_judge_raw[1]
if domain_judge.to_s == web_url_domain.to_s
#the same domain
src_matched = nil
else
#not the same domain
src_matched[0]
end
end
else
src_matched = nil
end
end
end
iframe_src_hide = iframe_src_hide_raw.compact
ad_hide_num = iframe_src_hide.size
$hide_ads_num = $hide_ads_num + ad_hide_num
這部分功能是:檢測頁面中所有的第三方隱藏廣告绍填。所謂隱藏廣告栖疑,就是其iframe
標(biāo)簽中的height
和width
屬性的值均為0px
遇革,或者display
屬性的值為none
,此時在頁面中并不顯示這個第三方廣告澳淑。
這部分代碼中杠巡,有一個點需要說明:
src_matched_hide = src_match[1].gsub(/\&\;/,"&")
這句代碼的功能是將得到的src
網(wǎng)址中的&
替換為&
雇寇。這是因為,在HTML中嫩海,預(yù)留字符必須被替換為字符實體囚痴。這里對HTML字符實體進行了較為詳細(xì)的介紹深滚。
在本例中涣觉,我們通過正則表達(dá)式得到的url中血柳,最常用的&
被轉(zhuǎn)義成了&
难捌,因此需要對其進行修正。而其他的字符實體因為在url中使用較少根吁,此處沒有進行更多的校驗婴栽。
功能塊四
#--------------------------------------------------------
#select the third party ads url from iframe.src
#iframe_src:ad url
#ad number
#--------------------------------------------------------
iframe_src_show_raw = match_iframe.map do |ifr|
if (src_match = ifr[0].to_s.match(/(<\s*iframe\s.*?(src=\"(.*?)\".*?>))/) )
src_matched_hide = src_match[1].gsub(/\&\;/,"&")
hide_condition_1 = src_matched_hide.match(/.*?\swidth\s*\=\s*\"\s*0\s*px\s*\"\s.*?height\s*=\s*\"\s*0\s*px\s*\".*/)
hide_condition_2 = src_matched_hide.match(/.*?\sheight\s*\=\s*\"\s*0\s*px\s*\"\s.*?width\s*=\s*\"\s*0\s*px\s*\".*/)
hide_condition_3 = src_matched_hide.match(/.*?style\s*=\s*\".*?width\s*:\s*0\s*px\s*;.*?height\s*:\s*0\s*px.*?\"/)
hide_condition_4 = src_matched_hide.match(/.*?style\s*=\s*\".*?height\s*:\s*0\s*px\s*;.*?width\s*:\s*0\s*px.*?\"/)
hide_condition_5 = src_matched_hide.match(/.*?\sdisplay\s*=\s*\"\s*none\s*\"\s*/)
hide_condition_6 = src_matched_hide.match(/.*?style\s*=\s*\".*?display\s*:\s*none\s*.*?\"/)
unless (hide_condition_1 || hide_condition_2 || hide_condition_3 || hide_condition_4 || hide_condition_5 || hide_condition_6)
src_matched = src_match[3].gsub(/\&\;/,"&")
src_matched = src_matched.match(/https?\:\/\/(.*)\/.*/)
if src_matched
domain_judge_raw = src_matched[0].to_s.match(/https?\:\/\/(.*?)\/.*?/)
domain_judge = domain_judge_raw[1]
if domain_judge.to_s == web_url_domain.to_s
#the same domain
src_matched = nil
else
#not the same domain
src_matched[0]
end
end
else
src_matched = nil
end
end
end
iframe_src_show = iframe_src_show_raw.compact
ad_show_num = iframe_src_show.size
#--------------------------------------------------------
#all ads
#--------------------------------------------------------
iframe_src = iframe_src_hide + iframe_src_show
ad_num = iframe_src.size
$all_ads_num = $all_ads_num + ad_num
這部分功能是:獲得所有非隱藏的第三方廣告的數(shù)據(jù)愚争,計算其數(shù)量轰枝;之后與隱藏的廣告數(shù)據(jù)整合,得到全部廣告的數(shù)據(jù)步淹。
功能塊五
#--------------------------------------------------------
#select ad domian
#src_domain:ad url domain
#--------------------------------------------------------
src_domain_raw = iframe_src.map do |sr|
if (domain_match = sr.to_s.match(/https?\:\/\/(.*?)\/.*?/) )
domain_matched = domain_match[1]
end
end
src_domain = src_domain_raw.compact
這部分功能是:根據(jù)獲得的所有廣告數(shù)據(jù)诚撵,獲得這些廣告的域寿烟。
功能塊六
#--------------------------------------------------------
#file operation
#--------------------------------------------------------
sheet[$web_num + 0,0] = "Web url"
sheet[$web_num + 0,1] = web_url
sheet[$web_num + 1,0] = "The num of ads"
sheet[$web_num + 1,1] = ad_num
sheet[$web_num + 1,2] = "The num of hide ads"
sheet[$web_num + 1,3] = ad_hide_num
sheet[$web_num + 2,0] = "The url domain of ads"
sheet[$web_num + 2,1] = "The url of ads" + "(The top " + String(ad_hide_num) + " are hidden ads)"
ad_num.times do |n|
i = n + 3 + $web_num
sheet[i,0] = src_domain[n]
sheet[i,1] = iframe_src[n]
end
$web_num = $web_num + ad_num + 3 + 1
puts "This page has searched successfully: #{web_url_para}"
這部分功能是文件操作,負(fù)責(zé)將得到的數(shù)據(jù)寫入表格中缝其。
至此徘六,這個方法的內(nèi)容已經(jīng)全部介紹完了待锈。盡管我們將這部分內(nèi)容全部放在一個方法中,但由于全局變量的引入,這部分內(nèi)容并不能完全的獨立阳惹。
(四)代碼執(zhí)行部分
File.open(web_file) do |fil|
if fil
fil.each do |url|
begin
search_ads(driver, url, sheet)
rescue
puts "This page has searched unsuccessfully: #{url}"
puts "Please waiting process..."
driver.quit
# driver = Selenium::WebDriver.for :chrome
driver = Selenium::WebDriver.for :firefox
puts "Start the next web url"
next
end
end
end
end
sheet[0,0] = "The ads number of all pages"
sheet[0,1] = $all_ads_num
sheet[0,2] = "The hide ads number of all pages"
sheet[0,3] = $hide_ads_num
excel_fil.write "ad_file.xls"
puts "Detection is complete!"
driver.quit
這部分負(fù)責(zé)讀取txt文件中的網(wǎng)址莹汤,并依次執(zhí)行上述方法颠印,獲得我們所需的數(shù)據(jù)后將其寫入表格中线罕。
這里,我們加入了異常處理喇闸。對于我們的功能询件,一些網(wǎng)站會禁止通過selenium訪問,有時由于網(wǎng)絡(luò)原因也會導(dǎo)致訪問時間過長而失敗刻蟹,因此需要添加異常處理嘿辟,從而保證程序能夠正確地運行下去。
四.腳本使用
已經(jīng)完成的腳本文件在環(huán)境配置成功后英古,可以直接使用昙读,整個工程中共有三個文件:
- detection_ad.rb:
可執(zhí)行文件箕戳,在終端terminal中(windows下為cmd)執(zhí)行命令:
工程即可正常運行国撵。> ruby detection_ad.rb
- weburl.txt:
這個文件用來放置待檢測的網(wǎng)頁網(wǎng)址,每一行僅能放置一個網(wǎng)址壮虫。程序運行后,腳本會打開weburl.txt
文件剩拢,并依次對文件中的所有網(wǎng)址進行檢測饶唤。
當(dāng)需要修改此文件名稱時募狂,需要在腳本中修改相關(guān)代碼,將weburl.txt
修改成自己需要的名稱:web_file = "weburl.txt"
- ad_file.xls:
這個文件用于保存數(shù)據(jù)性穿,腳本運行后處理得到的所有數(shù)據(jù)會全部寫入這個文件中雷滚。如果需要將最終數(shù)據(jù)寫入到其他名稱的xls
文件中,只需要修改detection_ad.rb
文件中相關(guān)代碼呆万,將ad_file.xls
改為自己需要的名稱:excel_fil.write "ad_file.xls"
五.總結(jié)
到這里桑嘶,我們的整個工程就全部完成了躬充,我們希望達(dá)到的目的也都實現(xiàn)了。但這里還有一些疑問或是問題有待解決:
- 效率問題:盡管可以實現(xiàn)第三方網(wǎng)頁檢測以政,但程序執(zhí)行的速度相對很慢
- 代碼并沒有封裝的很好伴找,且引入了全局變量,當(dāng)這個腳本用在比較大型的且復(fù)雜的工程中時抖誉,很可能出現(xiàn)問題
- 代碼質(zhì)量有待提高
- 由于時間比較急袒炉,對代碼中變量的名稱沒有使用的很準(zhǔn)確樊零,注釋也不是很清晰孽文,需要修正
暫時想到這么多芋哭,后續(xù)需要認(rèn)真糾正和學(xué)習(xí)郁副。