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