外話
今天把 《CSS REFACTORING》(中文名叫《CSS重構(gòu):樣式表性能調(diào)優(yōu)》)電子書粗略的瀏覽了一遍,這本書很薄,150頁左右,首先是介紹了什么是重構(gòu)并舉了兩個簡單的重構(gòu)例子,然后介紹了CSS的選擇器優(yōu)先級,再然后介紹了CSS的最佳實踐, 再然后就介紹如何重置瀏覽器的默認樣式,最后比較虛的、純理論的介紹了CSS重構(gòu)的策略,然后就沒有然后了。這書整體內(nèi)容很簡單,但是,其中對于 CSS選擇器優(yōu)先級計算 作了比較深入的講解。
什么是選擇器優(yōu)先級(Specificity)
直接復制了MDN對優(yōu)先級的定義 上的解釋:
瀏覽器通過優(yōu)先級來判斷哪一些屬性值與一個元素最為相關(guān),從而在該元素上應(yīng)用這些屬性值。優(yōu)先級是基于不同種類選擇器組成的匹配規(guī)則。
這句話也是很抽象,暫且先不管它了。但是我們可以先看一個例子:
<div id="content" class="content"> 我是什么顏色 </div>
#content { color: #f00; } .content { color: #0f0; }
那最后文字是什么顏色呢?答案很簡單:紅色。這就涉及到了優(yōu)先級問題,同一塊內(nèi)容,我們同時用了 ID選擇器 和 類選擇器,因為 ID選擇器 優(yōu)先級大于 類選擇器 , 所以最終顯示為紅色。
優(yōu)先級的計算規(guī)則
相信每位寫過CSS的朋友都知道,CSS選擇器的優(yōu)先級關(guān)系是:
內(nèi)聯(lián) > ID選擇器 > 類選擇器 > 標簽選擇器。
但是,瀏覽器具體的優(yōu)先級算法是怎樣的?可能還有些人不知道 。《CSS REFACTORING》 中提到了算法的過程 。
A specificity is determined by plugging numbers into (a, b, c, d):
翻譯過來就是
優(yōu)先級是由 A 、B、C、D 的值來決定的,其中它們的值計算規(guī)則如下:
這樣子直接看好像也還是很明白 ,那先上個例子:
#nav-global > ul > li > a.nav-link
套用上面的算法,依次求出 A B C D 的值:
上面算出的A 、 B、C、D 可以簡記作:(0, 1, 1, 3)。
為了熟悉掌握優(yōu)先級算法 ,我們再來做一些練習:
li /* (0, 0, 0, 1) */ ul li /* (0, 0, 0, 2) */ ul ol+li /* (0, 0, 0, 3) */ ul ol+li /* (0, 0, 0, 3) */ h1 + *[REL=up] /* (0, 0, 1, 1) */ ul ol li.red /* (0, 0, 1, 3) */ li.red.level /* (0, 0, 2, 1) */ a1.a2.a3.a4.a5.a6.a7.a8.a9.a10.a11 /* (0, 0, 11,0) */ #x34y /* (0, 1, 0, 0) */ li:first-child h2 .title /* (0, 0, 2, 2) */ #nav .selected > a:hover /* (0, 1, 2, 1) */ html body #nav .selected > a:hover /* (0, 1, 2, 3) */
OK, 現(xiàn)在已經(jīng)弄清楚了優(yōu)先級是怎么算的了。但是,還有一個問題,怎么比較兩個優(yōu)先級的高低呢?
比較規(guī)則是: 從左往右依次進行比較 ,較大者勝出,如果相等,則繼續(xù)往右移動一位進行比較 。如果4位全部相等,則后面的會覆蓋前面的
再來看一下例子:
<div class="nav-list" id="nav-list"> <div class="item">nav1</div> <div class="item">nav2</div> </div>
#nav-list .item { color: #f00; } .nav-list .item { color: #0f0; }
算出 #nav-list .item 的優(yōu)先級是 (0, 1, 1, 0), .nav-list .item 的優(yōu)先級是 (0, 0, 2, 0)。 左邊第一位都是0, 再看看左邊第二位,前者是1,后者是0, 所以(0, 1, 1, 0) 的大于 (0, 0, 2, 0) ,即 #nva-list .item 大于 .nav-list .item,所以字體會是紅色。
優(yōu)先級的特殊情況
經(jīng)過上面的優(yōu)先級計算規(guī)則,我們可以知道內(nèi)聯(lián)樣式的優(yōu)先級是最高的,但是外部樣式有沒有什么辦法覆蓋內(nèi)聯(lián)樣式呢?有的,那就要 !important 出馬了。因為一般情況下,很少會使用內(nèi)聯(lián)樣式 ,所以 !important 也很少會用到!如果不是為了要覆蓋內(nèi)聯(lián)樣式,建議盡量不要使用 !important 。、
那可能有人會想,那如果我內(nèi)聯(lián)樣式用了 !important,是不是外部樣式就沒有辦法了呢?比如下面的代碼:
<div class="app" style="color:#f00!important">666</div>
.app { color: 0f0!important; }
是的,你贏了,這時候內(nèi)聯(lián)樣式已經(jīng)強大到不管你外部樣式怎么寫都無法覆蓋它了。這種情況在實際代碼中是要杜絕的!記住,千萬不要在內(nèi)聯(lián)樣式中使用 !important
最后 , !important 真的是的無法超越的王者嗎?其實不是的,一些情況,我們可以超越 !important, 請看下面的例子:
<div class="box" style="background: #f00; width: 300px!important;"><div>
.box { max-width: 100px; }
這時候 .box 的寬度只有 100px , 而不是 300px, 可見,max-width 可以超越 width!important!但是,這實際上不是優(yōu)先級的問題,因為優(yōu)先級是比較相同屬性的,而 max-width 和 width 是兩個不同的問題。之所以舉這個例子,是要告訴大家,有時候不管怎么設(shè)置容器的 width 都不生效,檢查一下是不是有人寫了 max-width 坑了你哈。
OK,優(yōu)先級先寫到這里啦,朋友們有問題歡迎留言討論~ 作者:漁歌。
家都知道display可以轉(zhuǎn)換元素類型,但是大多人其實對于display的屬性值,比較熟悉的只是block和inline以及inline-block和none,對于其他屬性值,了解都比較一般,在平時開發(fā)過程中也不太用得到其他的屬性值,但是每次用這個屬性的時候,腦海里都會冒出來,其他的屬性值,設(shè)置了會是怎么樣、有什么樣的特點,這個奇奇怪怪的想法,所以找了個時間,寫下這篇文章, 跟我有相同可愛想法的伙伴,如果感興趣的,可以駐步瞄一眼喲;
display屬性:規(guī)定元素應(yīng)該生成的框的類型(改變元素的類型,使用display屬性)。
ps:以下就是每個屬性的詳解了,啦啦啦啦啦啦啦;
屬性值詳解:
1、none:此元素不會被顯示;
(1) none此單詞的意思是沒有一個、毫無的意思;所以當display的屬性值設(shè)置為none的時候,表示的是沒有框類型,沒有框類型的元素,是無法在瀏覽器中顯示的,就實現(xiàn)隱藏元素的作用了;
示例如下:
html結(jié)構(gòu):
<div></div>
<p>我是p,測試div消失后,會不會占據(jù)瀏覽器空間</p>
css樣式:
<style>
div{
width:100px;
height:100px;
background:violet;
/*設(shè)置div的屬性值為none,觀察div是否會隱藏不可見*/
display: none;
}
p{
background:yellowgreen
}
</style>
以上代碼效果可以看出,div設(shè)置none之后,實現(xiàn)了完全消失并且不占據(jù)瀏覽器的空間效果;
(2)有很多標簽,display的屬性值默認是none,比如 head meta style link等等;
(3)項目應(yīng)用中,做二級導航效果或者鼠標懸停效果動態(tài)時,會經(jīng)常用到這個屬性值,下次我們寫一個好玩的二級效果再來展示這個屬性值的作用;
2、block:此元素將顯示為塊級元素,此元素前后會帶有換行符。
示例如下:
html結(jié)構(gòu):
<em>我原本是行內(nèi)元素</em>
css樣式:
<style>
em{
width:100px;
height:100px;
background:tomato;
/*em本來是行內(nèi)元素元素,現(xiàn)在使用display屬性轉(zhuǎn)換為塊狀元素 */
display: block;
}
</style>
3、inline 默認。此元素會被顯示為內(nèi)聯(lián)元素,元素前后沒有換行符。
示例如下:
html結(jié)構(gòu):
<div>我原本是塊狀元素</div>
<span>用來測試的行內(nèi)元素span</span>
css樣式:
<style>
div{
width:80px;
height:80px;
background:coral;
/*div標簽本來是塊狀元素,現(xiàn)在使用display屬性轉(zhuǎn)換為行內(nèi)元素;*/
display: inline;
}
span{
background:cornflowerblue
}
</style>
4、inline-block 行內(nèi)塊元素(CSS2.1 新增的值)
說明:行內(nèi)塊元素既具備行內(nèi)元素的特性也具備塊狀元素的特性,具備行內(nèi)元素前后沒有換行符可以在一行內(nèi)并列顯示的特性,具備塊狀元素可以正確解釋盒模型屬性的特性。
示例如下:
div塊狀元素,span行內(nèi)元素,使用此屬性值轉(zhuǎn)換為行內(nèi)塊元素;
html結(jié)構(gòu):
<div>我原本是塊狀元素</div>
<span>我原本是行內(nèi)元素</span>
css樣式:
div,span{
/*div塊狀元素,span行內(nèi)元素,使用此屬性值轉(zhuǎn)換為行內(nèi)塊元素;*/
display: inline-block;
}
div{
width:100px;
height:100px;
background:tomato;
}
span{
width:100px;
height:100px;
background:turquoise;
}
5、list-item 此元素會作為列表顯示。
(1) 此屬性值表示將元素顯示為列表項標簽,li標簽?zāi)J的display的屬性值是list-item,display的屬性值為list-item的標簽也屬于塊狀元素;
示例如下:
(2) li標簽作為列表項標簽,前面會有列表項標記,下面給div標簽設(shè)置為list-item,div也會有列表項標記
html結(jié)構(gòu):
<ul>
<div>div0</div>
<li>li1</li>
<li>li2</li>
<li>li3</li>
</ul>
css樣式:
ul{
background:tomato
}
ul li{
border:1px solid turquoise
}
div{
/*給div標簽設(shè)置為list-item*/
display: list-item;
}
6、run-in 此元素會根據(jù)上下文作為塊級元素或內(nèi)聯(lián)元素顯示。
(1) 目前很少有瀏覽器支持run-in這個屬性值,并且在開發(fā)過程中用不到這個屬性值,不予過多的研究;
7、table 此元素會作為塊級表格來顯示(類似 <table>),表格前后帶有換行符。
(1)table標簽?zāi)J的元素類型是table,顯示為塊級表格,可以設(shè)置大小并且單獨占據(jù)一行;(2)當table標簽的元素類型是table時,并且設(shè)置寬度和高度之后,后代td標簽的寬度和高度,默認是由table根據(jù)內(nèi)容的多少去分配的;
(3) table屬于塊狀元素,但是對比別的塊狀元素,有自己的特點, table會單獨占據(jù)一行,但是在沒有設(shè)置width的情況下,不會與父元素同寬,而是根據(jù)內(nèi)容而定;
html結(jié)構(gòu):
<table>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</table>
<span>我是行內(nèi)元素span</span>
css樣式:
table{
border:2px solid red;
}
table td{
border:1px solid chocolate;
background:darkcyan
}
span{
background:cornflowerblue
}
(3)其他標簽設(shè)置display的屬性值為table,也不會具有表格的特性;
html結(jié)構(gòu):
<div>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</div>
css樣式:
div{
height:300px;
width:300px;
border:2px solid red;
/*將div設(shè)置為表格類型*/
display: table;
}
div td{
border:1px solid chocolate;
background:darkcyan
}
span{
background:cornflowerblue
}
8、inline-table 此元素會作為內(nèi)聯(lián)表格來顯示(類似 <table>),表格前后沒有換行符。
(1)將table顯示為行內(nèi)表格,具有行內(nèi)塊元素的特征,可以和別的行內(nèi)元素從左往右顯示也可以設(shè)置寬度和高度;
html結(jié)構(gòu):
<table>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</table>
<span>我是行內(nèi)元素span</span>
css樣式:
table{
border:2px solid red;
/*將table設(shè)置為inline-table*/
display: inline-block;
}
table td{
background:darkcyan;
}
span{
background:cornflowerblue
}
(2)將table標簽設(shè)置為inline-table之后,td標簽的寬度就不能是table根據(jù)內(nèi)容去分配了,需要單獨給td設(shè)置width和height屬性實現(xiàn),不然td的大小就是內(nèi)容的大小
HTML結(jié)構(gòu):
<table>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</table>
<span>我是行內(nèi)元素span</span>
css樣式:
table{
border:2px solid red;
/*將table設(shè)置為inline-table*/
display: inline-block;
width:300px;
height:300px;
}
table td{
background:darkcyan;
}
span{
background:cornflowerblue
}
9、table-caption 此元素會作為一個表格標題顯示(類似 <caption>)
(1)caption標簽display的屬性值是table-caption,能設(shè)置寬度高度盒模型屬性等,屬于塊狀元素;
html結(jié)構(gòu):
<table>
<caption>我是表格的標題標簽</caption>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</table>
css樣式:
table{
border:2px solid red;
width:300px;
height:300px;
}
table td{
background:darkcyan;
}
table caption{
width:400px;
height:50px;
background:cyan;
margin:10px 0;
padding:10px;
line-height:50px;
}
(2)其他標簽也可以設(shè)置此屬性值,但是不具備表格標題的作用
10、table-header-group 此元素會作為一個或多個行的分組來顯示(類似 <thead>)。
html結(jié)構(gòu):
<table>
<thead>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</thead>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</table>
css樣式:
table{
border:2px solid red;
}
table td{
background:darkcyan;
width:100px;
height:100px;
}
table thead{
/*以下屬性設(shè)置無效*/
height:60px;
width:400px;
margin:100px;
}
11、table-row-group 此元素會作為一個或多個行的分組來顯示(類似 <tbody>)。
<table>
<thead>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</tbody>
</table>
css樣式:
table{
border:2px solid red;
width:300px;
height:300px;
}
table td{
background:darkcyan;
}
table tbody{
/*以下屬性設(shè)置無效*/
height:60px;
width:400px;
margin:100px;
}
12、table-footer-group 此元素會作為一個或多個行的分組來顯示(類似 <tfoot>)。
(1)tfoot標簽display的屬性值是table-footer-group;
(2)tfoot標簽的大小根據(jù)table自動分配,或者根據(jù)td而定,本身不能設(shè)置大小或者其他邊距
(3)其他標簽可以設(shè)置此屬性值,但是不具表格表尾標簽的作用
HTML結(jié)構(gòu):
<table>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tfoot>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</tfoot>
</table>
css樣式:
table{
border:2px solid red;
width:300px;
height:300px;
}
table td{
background:darkcyan;
}
/*以下屬性設(shè)置無效*/
table tfoot{
height:60px;
width:400px;
margin:100px;
}
13、table-row 此元素會作為一個表格行顯示(類似 <tr>)。
(1) tr標簽display的屬性值是table-row
(2) tr標簽設(shè)置height有效,width 邊距設(shè)置無效,具體的大小根據(jù)table和td而定;
(3) 其他標簽可以設(shè)置此屬性值,但是不具表格行標簽的作用
HTML結(jié)構(gòu):
<table>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</table>
css樣式:
table{
border:2px solid red;
}
table td{
background:darkcyan;
}
table tr{
border:1px solid chartreuse;
margin:20px;
height:100px;
width:300px;
margin:10px;
}
14、table-cell 此元素會作為一個表格單元格顯示(類似 <td> 和 <th>)
(1)td , th標簽display的屬性值是table-cell
(2)設(shè)置寬度、高度、邊框、內(nèi)邊距有效,外邊距無效,單元格之間的間距,使用border-spacing實現(xiàn)
HTML結(jié)構(gòu):
<table>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</table>
css樣式:
<style>
table{
border:2px solid red;
border-spacing: 10px;
}
table td{
background:darkcyan;
width:100px;
height:100px;
}
</style>
(3)其他標簽可以設(shè)置此屬性值,但是不具單元格標簽的作用
html結(jié)構(gòu):
<table>
<tr>
<span>1</span>
<span>2</span>
<span>3</span>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</table>
css樣式:
table{
border:2px solid red;
border-spacing: 10px;
}
table td{
background:darkcyan;
width:100px;
height:100px;
}
span{
display: table-cell;
width:100px;
height:100px;
background:darkcyan;
}
15、table-column-group 此元素會作為一個或多個列的分組來顯示(類似 <colgroup>)。
(1)colgroup 標簽 display的屬性值是table-column-group
(2)此標簽的特點是對列進行分組的,給分組的列設(shè)置樣式;
HTML結(jié)構(gòu):
<table>
<colgroup span="1"></colgroup>
<colgroup span="2"></colgroup>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</table>
css樣式:
table{
border:2px solid red;
border-spacing: 10px;
}
table td{
background:darkcyan;
}
table colgroup:nth-child(1){
width:100px;
height:100px;
background:pink
}
table colgroup:nth-child(2){
width:50px;
height:50px;
background:green
}
16、table-column 此元素會作為一個單元格列顯示(類似 <col>)
(1)col 標簽 display的屬性值是table-column
(2)此標簽的特點是對單元格列設(shè)置效果;
HTML結(jié)構(gòu):
<table>
<col span="1">
<col span="2">
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
<tr>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</table>
css樣式:
<style>
table{
border:2px solid red;
border-spacing: 10px;
}
table td{
width:100px;
height:100px;
}
table col:nth-child(1){
background:pink
}
table col:nth-child(2){
background:green
}
</style>
17、inherit 規(guī)定應(yīng)該從父元素繼承 display 屬性的值。
說明:此屬性值是所有css屬性都有的值,能被繼承的屬性,自然可以繼承,不能被繼承的,設(shè)置了此屬性值也不能實現(xiàn)繼承;
18、compact CSS 中有值 compact,不過由于缺乏廣泛支持,已經(jīng)從 CSS2.1 中刪除。
19、marker CSS 中有值 marker,不過由于缺乏廣泛支持,已經(jīng)從 CSS2.1 中刪除。
通過以上的測試,可以總結(jié)出:
display的屬性值為block,table的標簽都為塊狀元素;
display的屬性值為inline,inline-table,inline-block的標簽為行內(nèi)級元素;
表格中的標簽對應(yīng)的那些display的屬性值,其他的標簽也可以設(shè)置,但是都不具備表格標簽的功能和特征,所以表格中的標簽對應(yīng)的display的屬性值,不能泛用,相當于一種特殊的存在;
們可以嘗試分析Ajax來抓取了相關(guān)數(shù)據(jù),但是并不是所有的頁面都是可以分析Ajax來就可以完成抓取的,比如淘寶。它的整個頁面數(shù)據(jù)確實也是通過Ajax獲取的,但是這些Ajax接口參數(shù)比較復雜,可能會包含加密密鑰等參數(shù),所以我們?nèi)绻胱约簶?gòu)造Ajax參數(shù)是比較困難的,對于這種頁面我們最方便快捷的抓取方法就是通過Selenium,本節(jié)我們就來用Selenium來模擬瀏覽器操作,抓取淘寶的商品信息,并將結(jié)果保存到MongoDB。
首先我們來看下淘寶的接口,看看它的接口相比一般Ajax多了怎樣的內(nèi)容。
打開淘寶頁面,搜索一個商品,比如iPad,此時打開開發(fā)者工具,截獲Ajax請求,我們可以發(fā)現(xiàn)會獲取商品列表的接口。
它的鏈接包含了幾個GET參數(shù),如果我們要想構(gòu)造Ajax鏈接直接請求再好不過了,它的返回內(nèi)容是Json格式。
但是這個Ajax接口包含了幾個參數(shù),其中_ksTS、rn參數(shù)不能直接發(fā)現(xiàn)其規(guī)律,如果我們要去探尋它的生成規(guī)律也不是做不到,但這樣相對會比較繁瑣,所以如果我們直接用Selenium來模擬瀏覽器的話就不需要再關(guān)注這些接口參數(shù)了,只要在瀏覽器里面可以看到的我們都可以爬取。這也是為什么我們選用Selenium爬取淘寶的原因。
我們本節(jié)的目標是爬取商品信息,例如:
這樣的一個結(jié)果就包含了一個商品的基本信息,包括商品圖片、名稱、價格、購買人數(shù)、店鋪名稱、店鋪所在地,我們要做的就是將這些信息都抓取下來。
抓取入口就是淘寶的搜索頁面,這個鏈接是可以直接構(gòu)造參數(shù)訪問的,例如如果搜索iPad,就可以直接訪問https://s.taobao.com/search?q=iPad,呈現(xiàn)的就是第一頁的搜索結(jié)果,如圖所示:
如果想要分頁的話,我們注意到在頁面下方有一個分頁導航,包括前5頁的鏈接,也包括下一頁的鏈接,同時還有一個輸入任意頁碼跳轉(zhuǎn)的鏈接,如圖所示:
在這里商品搜索結(jié)果一般最大都為100頁,我們要獲取的每一頁的內(nèi)容,只需要將頁碼從1到100順次遍歷即可,頁碼數(shù)是確定的。所以在這里我們可以直接在頁面跳轉(zhuǎn)文本框中輸入要跳轉(zhuǎn)的頁碼,然后點擊確定按鈕跳轉(zhuǎn)即可到達頁碼頁碼對應(yīng)的頁面。
在這里我們不直接點擊下一頁的原因是,一旦爬取過程中出現(xiàn)異常退出,比如到了50頁退出了,我們?nèi)绻c擊下一頁就無法快速切換到對應(yīng)的后續(xù)頁面,而且爬取過程中我們也需要記錄當前的頁碼數(shù),而且一旦點擊下一頁之后頁面加載失敗,我們還需要做異常檢測檢測當前頁面是加載到了第幾頁,因此整個流程相對復雜,所以在這里我們直接選用跳頁的方式來爬取頁面。
當我們成功加載出某一頁商品列表時,利用Selenium即可獲取頁面源代碼,然后我們再用相應(yīng)的解析庫解析即可,在這里我們選用PyQuery進行解析。
下面我們用代碼來實現(xiàn)一下整個抓取過程。
獲取商品列表
首先我們需要構(gòu)造一個抓取的URL,https://s.taobao.com/search?q=iPad,URL非常簡潔,參數(shù)q就是要搜索的關(guān)鍵字,我們只需要改變鏈接的參數(shù)q即可獲取不同商品的列表,在這里我們將商品的關(guān)鍵字定義成一個變量,然后構(gòu)造出這樣的一個URL。
構(gòu)造出URL之后我們就需要用Selenium進行抓取了,我們實現(xiàn)如下抓取列表頁的方法:
from selenium import webdriverfrom selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from urllib.parse import quote
browser=webdriver.Chrome()
wait=WebDriverWait(browser, 10)
KEYWORD='iPad'
def index_page(page):
"""
抓取索引頁
:param page: 頁碼
"""
print('正在爬取第', page, '頁')
try:
url='https://s.taobao.com/search?q=' + quote(KEYWORD)
browser.get(url)
if page > 1:
input=wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, '#mainsrp-pager div.form > input')))
submit=wait.until(
EC.element_to_be_clickable((By.CSS_SELECTOR, '#mainsrp-pager div.form > span.btn.J_Submit')))
input.clear()
input.send_keys(page)
submit.click()
wait.until(
EC.text_to_be_present_in_element((By.CSS_SELECTOR, '#mainsrp-pager li.item.active > span'), str(page)))
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.m-itemlist .items .item')))
get_products()
except TimeoutException:
index_page(page)
在這里我們首先構(gòu)造了一個WebDriver對象,使用的瀏覽器是Chrome,然后指定一個關(guān)鍵詞,如iPad,然后我們定義了一個get_index()方法,用于抓取商品列表頁。
在該方法里我們首先訪問了這個鏈接,然后判斷了當前的頁碼,如果大于1,那就進行跳頁操作,否則等待頁面加載完成。
等待加載我們使用了WebDriverWait對象,它可以指定等待條件,同時指定一個最長等待時間,在這里指定為最長10秒。如果在這個時間內(nèi)成功匹配了等待條件,也就是說頁面元素成功加載出來了,那就立即返回相應(yīng)結(jié)果并繼續(xù)向下執(zhí)行,否則到了最大等待時間還沒有加載出來就直接拋出超時異常。
比如我們最終要等待商品信息加載出來,在這里就指定了presence_of_element_located這個條件,然后傳入了 .m-itemlist .items .item 這個選擇器,而這個選擇器對應(yīng)的頁面內(nèi)容就是每個商品的信息塊,可以到網(wǎng)頁里面查看一下。如果加載成功,就會執(zhí)行后續(xù)的get_products()方法,提取商品信息。
關(guān)于翻頁的操作,我們在這里是首先獲取了頁碼輸入框,賦值為input,然后獲取了提交按鈕,賦值為submit,分別是下圖中的兩個元素:
首先我們清空了輸入框,調(diào)用clear()方法即可,隨后調(diào)用send_keys()方法將頁碼填充到輸入框中,然后點擊確定按鈕即可。
那么怎樣知道有沒有跳轉(zhuǎn)到對應(yīng)的頁碼呢?我們可以注意到成功跳轉(zhuǎn)某一頁后頁碼都會高亮顯示:
我們只需要判斷當前高亮的頁碼數(shù)是當前的頁碼數(shù)即可,所以在這里使用了另一個等待條件 text_to_be_present_in_element,它會等待某一文本出現(xiàn)在某一個節(jié)點里面即返回成功,在這里我們將高亮的頁碼節(jié)點對應(yīng)的CSS選擇器和當前要跳轉(zhuǎn)的頁碼通過參數(shù)傳遞給這個等待條件,這樣它就會檢測當前高亮的頁碼節(jié)點里是不是我們傳過來的頁碼數(shù),如果是,那就證明頁面成功跳轉(zhuǎn)到了這一頁,頁面跳轉(zhuǎn)成功。
那么這樣,剛才我們所實現(xiàn)的get_index()方法就可以做到傳入對應(yīng)的頁碼,然后加載出對應(yīng)頁碼的商品列表后,再去調(diào)用get_products()方法進行頁面解析。
解析商品列表
接下來我們就可以實現(xiàn)get_products()方法來解析商品列表了,在這里我們直接獲取頁面源代碼,然后用PyQuery進行解析,實現(xiàn)如下:
from pyquery import PyQuery as pqdef get_products():
"""
提取商品數(shù)據(jù)
"""
html=browser.page_source
doc=pq(html)
items=doc('#mainsrp-itemlist .items .item').items()
for item in items:
product={
'image': item.find('.pic .img').attr('data-src'),
'price': item.find('.price').text(),
'deal': item.find('.deal-cnt').text(),
'title': item.find('.title').text(),
'shop': item.find('.shop').text(),
'location': item.find('.location').text()
}
print(product)
save_to_mongo(product)
首先我們調(diào)用了page_source屬性獲取了頁碼的源代碼,然后構(gòu)造了PyQuery解析對象,首先我們提取了商品列表,使用的CSS選擇器是 #mainsrp-itemlist .items .item,它會匹配到整個頁面的每個商品,因此它的匹配結(jié)果是多個,所以在這里我們又對它進行了一次遍歷,用for循環(huán)將每個結(jié)果分別進行解析,在這里每個結(jié)果我們用for循環(huán)把它賦值為item變量,每個item變量都是一個PyQuery對象,然后我們再調(diào)用它的find()方法,傳入CSS選擇器,就可以獲取單個商品的特定內(nèi)容了。
比如在這里我們查看一下商品信息源碼,如圖所示:
在這里我們觀察一下商品圖片的源碼,它是一個 img 節(jié)點,包含了id、class、data-src、alt、src等屬性,在這里我們之所以可以看到這張圖片是因為它的src屬性被賦值為圖片的URL,在這里我們就把它的src屬性提取出來就可以獲取商品的圖片了,不過這里我們還注意到有一個data-src屬性,它的內(nèi)容也是圖片的URL,觀察后發(fā)現(xiàn)此URL是圖片的完整大圖,而src是壓縮后的小圖,所以這里我們抓取data-src屬性來作為商品的圖片。
所以我們需要先利用find()方法先找到圖片的這個節(jié)點,然后再調(diào)用attr()方法獲取商品的data-src屬性即可,這樣就成功提取了商品圖片鏈接。然后我們用同樣的方法提取商品的價格、成交量、名稱、店鋪、店鋪所在地等信息,然后將所有提取結(jié)果賦值為一個字典,叫做product,隨后調(diào)用save_to_mongo()將其保存到MongoDB即可。
保存到MongoDB
接下來我們再將商品信息保存到MongoDB,實現(xiàn)如下:
MONGO_URL='localhost'MONGO_DB='taobao'
MONGO_COLLECTION='products'
client=pymongo.MongoClient(MONGO_URL)
db=client[MONGO_DB]
def save_to_mongo(result):
"""
保存至MongoDB
:param result: 結(jié)果
"""
try:
if db[MONGO_COLLECTION].insert(result):
print('存儲到MongoDB成功')
except Exception:
print('存儲到MongoDB失敗')
我們首先創(chuàng)建了一個MongoDB的連接對象,然后指定了數(shù)據(jù)庫,在方法里隨后指定了Collection的名稱,然后直接調(diào)用insert()方法即可將數(shù)據(jù)插入到MongoDB,此處的result變量就是在get_products()方法里傳來的product,包含了單個商品的信息,這樣我們就成功實現(xiàn)了數(shù)據(jù)的插入。
遍歷每頁
剛才我們所定義的get_index()方法需要接收一個參數(shù)page,page即代表頁碼數(shù),所以在這里我們再實現(xiàn)頁碼遍歷即可,代碼如下:
MAX_PAGE=100def main():
"""
遍歷每一頁
"""
for i in range(1, MAX_PAGE + 1):
index_page(i)
實現(xiàn)非常簡單,只需要調(diào)用一個for循環(huán)即可,在這里定義最大的頁碼數(shù)100,range()方法的返回結(jié)果就是1到100的列表,順次遍歷調(diào)用index_page()方法即可。
這樣我們的淘寶商品爬蟲就完成了,最后調(diào)用main()方法即可運行。
我們將代碼運行起來,可以發(fā)現(xiàn)首先會彈出一個Chrome瀏覽器,然后順次訪問淘寶頁面,然后控制臺便會輸出相應(yīng)的提取結(jié)果,這些商品信息結(jié)果都是一個字典形式,然后被存儲到了MongoDB里面。
但是此次爬取有個不太友好的地方就是Chrome瀏覽器,爬取過程必須要開啟一個Chrome瀏覽器確實不太方便,所以在這里我們還可以對接PhantomJS,只需要將WebDriver的聲明修改一下即可,但是注意這里必須要安裝好PhantomJS,如果沒有安裝可以參考第一章里的安裝方法說明。
將WebDriver聲明修改如下:
browser=webdriver.PhantomJS()
這樣在抓取過程中就不會有瀏覽器彈出了。
另外我們還可以設(shè)置緩存和禁用圖片加載的功能,進一步提高爬取效率,修改如下:
SERVICE_ARGS=['--load-images=false', '--disk-cache=true']browser=webdriver.PhantomJS(service_args=SERVICE_ARGS)
這樣我們就可以禁用PhantomJS的圖片加載同時開啟緩存,可以發(fā)現(xiàn)頁面爬取速度進一步提升。
本節(jié)代碼地址為:https://github.com/Python3WebSpider/TaobaoProduct
End.
來源:公眾號“Python愛好者社區(qū)”
運行人員:中國統(tǒng)計網(wǎng)小編(微信號:itongjilove)
微博ID:中國統(tǒng)計網(wǎng)
中國統(tǒng)計網(wǎng),是國內(nèi)最早的大數(shù)據(jù)學習網(wǎng)站,公眾號:中國統(tǒng)計網(wǎng)
http://www.itongji.cn
*請認真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。