以伯樂在線文章為爬取目標blog.jobbole.com,發現在"最新文章"選項中可看到所有文章
一般來說,可以用scrapy中自帶的xpath或者css來提取數據,定義在spiders/jobbole.py中的def parse(self, response)
import scrapy class JobboleSpider(scrapy.Spider): name = 'jobbole' allowed_domains = ['blog.jobbole.com'] start_urls = ['http://blog.jobbole.com/'] def parse(self, response): re_selector = response.xpath('//*[@id="post-110287"]/div[1]/h1/text()')
注意:因為jqury會生成額外的代碼,我們在源碼看到的代碼和頁面加載之后顯示的代碼可能不同,所以不要按層級一步步找,最好找到id,或者class來定位
小技巧:
1)當我們使用class來定位標簽時,可以在F12中用ctrl+F 查看這個class名字是否唯一
2)Xpath路徑可右鍵直接復制
一. Xpath常用方法
1. 常用規則如下
// 從當前節點選取子孫節點,如果符號前面沒路徑,表示整個文檔
/ 從當前節點選取直接子節點
. 選取當前節點
.. 選取當前節點父節點
@ 選取屬性
//* 整個HTML文本中的所有節點
例子1
<html><body><div>
<ul>
<li class="item-0"><a href="link1.html"><span>first item</span></a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html">third item</a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div></body></html>
1. 獲取父節點屬性
首先選中href屬性為link4.html的a節點,然后再獲取其父節點,然后再獲取其class屬性 result1 = response.xpath('//a[@href="link4.html"]/../@class') 我們也可以通過parent::來獲取父節點 result2 = response.xpath('//a[@href="link4.html"]/parent::*/@class')
注意:
//a表示html中的所有a節點,他們的href屬性有多個,這里[]的作用是屬性匹配,找到a的href屬性為link4.html的節點
2. 獲取節點內部文本
獲取class為item-1的li節點文本, result3 = response.xpath('//li[@class="item-0"]/a/text()') 返回結果為['first item', 'fifth item']
3. 屬性獲取
獲取所有li節點下的所有a節點的href屬性 result4 = response.xpath('//li/a/@href') 返回結果為['link1.html', 'link2.html', 'link3.html', 'link4.html', 'link5.html']
4. 按序選擇
result = response.xpath('//li[1]/a/text()') #選取第一個li節點 result = response.xpath('//li[last()]/a/text()') #選取最后一個li節點 result = response.xpath('//li[position()<3]/a/text()') #選取位置小於3的li節點,也就是1和2的節點 result = response.xpath('//li[last()-2]/a/text()') #選取倒數第三個節點
5. 節點軸選擇
1)返回第一個li節點的所有祖先節點,包括html,body,div和ul result = response.xpath('//li[1]/ancestor::*') 2)返回第一個li節點的<div>祖先節點 result = response.xpath('//li[1]/ancestor::div') 3)返回第一個li節點的所有屬性值 result = response.xpath('//li[1]/attribute::*') 4)首先返回第一個li節點的所有子節點,然后加上限定條件,選組href屬性為link1.html的a節點 result = response.xpath('//li[1]/child::a[@href="link1.html"]') 5)返回第一個li節點的所有子孫節點,然后加上只要span節點的條件 result = response.xpath('//li[1]/descendant::span') 6)following軸可獲得當前節點之后的所有節點,雖然使用了*匹配,但是又加了索引選擇,所以只獲取第2個后續節點,也就是第2個<li>節點中的<a>節點 result = response.xpath('//li[1]/following::*[2]') 7)following-sibling可獲取當前節點之后的所有同級節點,也就是后面所有的<li>節點 result = response.xpath('//li[1]/following-sibling::*')
6. 屬性多值匹配
<li class="li li-first"><a href="link.html">first item</a></li> result5 = response.xpath('//li[@class="li"]/a/text()') 返回值為空,因為這里HTML文本中li節點為class屬性有2個值li和li-first,如果還用之前的屬性匹配就不行了,需要用contain()函數 正確方法如下 result5 = response.xpath('//li[contains(@class, "li")]/a/text()') contains()方法中,第一個參數為屬性名,第二個參數傳入屬性值,只要此屬性名包含所傳入的屬性值就可完成匹配
7.
多屬性匹配,這里說一下不用框架的時候,xpath的常規用法
有時候我們需要多個屬性來確定一個節點,那么就需要同時匹配多個屬性,可用and來連接
from lxml import etree text = ''' <li class = "li li-first" name="item"><a href="link.html">first item</a></li>
''' html = etree.HTML(text) result6 = html.xpath('//li[contains(@class, "li") and @name="item"]/a/text()') print(result)
這里的li節點有class和name兩個屬性,需要用and操作符相連,然后置於中括號內進行條件篩選
二. 調試命令
cmd中執行如下代碼,即可進入調試命令行,這個命令已經取得了頁面中的原代碼,命令測試成功后即可放在def parse函數中
scrapy shell http://blog.jobbole.com/110287
開始調試,
1. 取得文章標題
>>> title = response.xpath('//div[@class="entry-header"]/h1/text()') >>> title [<Selector xpath='//div[@class="entry-header"]/h1/text()' data='2016 騰訊軟件開發面試題(部分)'>] >>> title.extract() ['2016 騰訊軟件開發面試題(部分)'] >>> title.extract()[0] '2016 騰訊軟件開發面試題(部分)'
>>> title.extract_first() '2016 騰訊軟件開發面試題(部分)'
說明
1)extract()方法會把原數據的selector類型轉變為列表類型
2)extract()會得到多個值,extract()[1]取第2個值
3)extract_first()得到第一個值,類型為字符串。extract_first(default='')如果沒取到返回默認值
2. 取得發表日期
>>> response.xpath("//p[@class='entry-meta-hide-on-mobile']/text()").extract()[0].strip().replace("·","").strip() '2017/02/18'
3. 點贊數,span標簽里有很多class名,選一個看起來像唯一的,測試一下,然后用contains()函數簡化操作
>>> response.xpath("//span[contains(@class, 'vote-post-up')]/h10/text()").extract() ['2'] >>> response.xpath("//span[contains(@class, 'vote-post-up')]/h10/text()").extract()[0] '2'
>>> int(response.xpath("//span[contains(@class, 'vote-post-up')]/h10/text()").extract()[0]) 2
4. 收藏數,要用正則,re模塊也是scrapy的內置模塊,注意要用非貪婪匹配,否則只會取到8
>>> response.xpath("//span[contains(@class, 'bookmark-btn')]/text()").extract()[0] ' 28 收藏'
>>> string = response.xpath("//span[contains(@class, 'bookmark-btn')]/text()").extract()[0] >>> import re >>> pattern = re.match(".*?(\d+).*", string) >>> pattern.group(1) '28'
可以簡寫為
>>> response.xpath("//span[contains(@class, 'bookmark-btn')]/text()").re('.*?(\d+).*') ['28'] >>> response.xpath("//span[contains(@class, 'bookmark-btn')]/text()").re('.*?(\d+).*')[0] '28'
5. 使用列表推導式取得一個標簽中的部分元素,如下取得職場和面試字樣。適用於有些文章沒評論標簽的情況


找到不是以"評論"結尾的元素 >>> response.xpath("//p[@class='entry-meta-hide-on-mobile']/a/text()").extract() ['職場', ' 9 評論 ', '面試'] >>> tag_list = response.xpath("//p[@class='entry-meta-hide-on-mobile']/a/text()").extract() >>> [element for element in tag_list if not element.strip().endswith("評論")] ['職場', '面試'] >>> tag_choose=[element for element in tag_list if not element.strip().endswith("評論")] >>> tags=",".join(tag_choose) >>> tags '職場,面試'
join()函數基本語法: 'sep'.join(seq)。表示以sep為分隔符,將seq中所有的元素合並成一個新的字符串
sep表示分隔符,可以為空;
seq表示要連接的數據,數據類型可以是列表,字符串,元組或者字典
三. css提取方式
1. css的幾個選擇器
li a
|
選取所有li下的所有a節點
|
ul + p
|
選擇ul后面的第一個p元素,ul和p是兄弟節點
|
div#container>ul
|
選取id為container的div標簽,下邊的第一個ul子元素
|
ul ~ p
|
選取與ul相鄰的所有p元素
|
a[title]
|
選取所有含有title屬性的a元素
|
a::attr(href)
|
獲取所有a元素的href屬性值
|
a[href="http://jobbole.com"]
|
選取所有href屬性為http://jobbole.com值的a元素
|
a[href*="jobble"]
|
選取所有href屬性包含jobbole的a元素
|
a[href^="http"]
|
選取所有href屬性值以http開頭的a元素
|
a[href$=".jpg"]
|
選取所有href屬性值以.jpg結尾的a元素
|
input[type=radio]:checked
|
選擇選中的radio的元素
|
div:not(#container)
|
選取所有id不等於container的div元素
|
li:nth-child(3)
|
選取第三個li元素
|
tr:nth-child(2n)
|
選取偶數位的tr元素
|
2. scrapy shell中使用css來提取數據
scrapy shell http://blog.jobbole.com/110287
1)提取標題,需要用到css的偽類 ::text
>>> response.css(".entry-header h1").extract() ['<h1>2016 騰訊軟件開發面試題(部分)</h1>'] >>> response.css(".entry-header h1::text").extract()[0] '2016 騰訊軟件開發面試題(部分)'
2)文章創建時間
>>> response.css("p.entry-meta-hide-on-mobile::text").extract()[0].strip().replace(" ·","") '2017/02/18'
注意:這里p和類名之間沒空格,表示類名為entry-meta-hide-on-mobile的p元素
3)點贊數,對於屬性多值匹配用css會很方便
>>> response.css(".vote-post-up h10::text").extract()[0] '2'
4) 收藏數,注意轉義字符的方向
>>> response.css(".bookmark-btn::text").extract()[0] ' 28 收藏'
>>> string = response.css(".bookmark-btn::text").extract()[0] >>> tag=re.match(".*?(\d+).*", string) >>> tag.group(1) '28'
其實正則re也是scrapy的內置模塊,可以簡寫為如下
>>> response.css(".bookmark-btn::text").re('.*?(\d+).*') ['28'] >>> response.css(".bookmark-btn::text").re('.*?(\d+).*')[0] '28'
5) 提取正文內容,一般把格式也取出來
response.css("div.entry").extract()[0]
6) 取得職場,評論,面試字樣
>>> response.css("p.entry-meta-hide-on-mobile a::text").extract() ['職場', ' 9 評論 ', '面試']