老早之前就聽說過python的scrapy。這是一個分布式爬蟲的框架,可以讓你輕松寫出高性能的分布式異步爬蟲。使用框架的最大好處當然就是不同重復造輪子了,因為有很多東西框架當中都有了,直接拿過來使用就可以了。scrapy 就是一個很棒的框架。最近在看崔慶才老師的博客http://cuiqingcai.com/ 的時候,發現了幾個寫的非常好的scrapy教程(http://cuiqingcai.com/4380.html,http://cuiqingcai.com/3952.html等,還有很多,大家可以自己去看),我看了半個小時就把以前看的scrapy基礎回憶起來了,而且又學到了很多新的東西,所以就手癢癢用scrapy寫了一個自己的爬蟲,記錄在這里。
腳本之家(http://www.jb51.net/)是我寫代碼查某個函數怎么怎么用(有時候忘了)經常去的地方,雖然站點界面像shi一樣,又有許多雜七雜八的廣告,不過說句公道話,還有有不少干貨的,大部分也都附有源代碼。今天我要爬取的就是腳本之家的文章。
在開始之前,先簡單介紹一下scrapy的常用命令吧。我不打算講的非常詳細,要想詳細了解,可以參考文檔(http://wiki.jikexueyuan.com/project/scrapy/)或者上面我說的幾篇博客。scrapy 創建一個 項目 使用的命令是: scrapy crawl project_name (project_name是你的項目名稱) 這個命令需要在cmd(windows)或者shell(linux)下鍵入,這就會在當前目錄下創建名稱為 project_name 的項目。然后 cd 到這個項目,輸入命令 genspider your_spider_name 來快速創建一個爬蟲,your_spider_name 為你的爬蟲的名字,注意這個名字必須是唯一的,這個命令在 project_name/project_name/spiders/目錄下 生成了一個 your_spider_name.py 文件,你的爬蟲就寫到這里啦。
常見的幾個文件作用如下:
spiders 文件夾用來存放你寫的爬蟲的腳本
items.py 用來定義你想要抓取的數據字段
middlewares.py 用來給scrapy增加一些額外的自定義的功能(比如后面要講的設置代理等等)
piplines.py 用來定義抓取數據的儲存方式
settings.py 是用來設置爬蟲參數的文件
打開 爬蟲文件,一般是已經給你 寫好了一個 類,類似這樣:
class JbzjSpiderSpider(scrapy.Spider): name = "jbzj_spider" allowed_domains = ["www.jb51.net"] base_url = "http://www.jb51.net" start_urls = [] def parse(self, response): pass
其中 name 是爬蟲的名字(唯一),allowed_domains 為允許抓取的域名,start_urls 為 起始抓取的列表,如果沒有特別指定抓取的url,就從start_urls列表中的地址抓取,類必須繼承自 scrapy.Spider,這是所有爬蟲都必須繼承的一個類,parse 是 scrapy.Spider 的一個方法,我們有的時候需要將他覆寫(override),這個方法是默認的回調函數(callback),如果沒有指定函數的回調函數的話,就會默認調用 parse函數。response 是解析 url 得到的相應,里面包含響應頭,響應網頁源碼,url等等,比如 response.body 得到 網頁源碼,response.url 得到響應的url。對於解析網頁,scrapy默認使用的方法是xpath 解析。比如可以直接使用 response.xpath("//a[@id="id1"]/@href").extract()得到id = id1的a標簽的href屬性(xpath的用法大家自行搜索,入門很快),使用 extract 方法返回的是一個列表。當然,除了使用xpath,你可以得到 response.body 之后,再使用你習慣解析html的方法(正則,css,bs4等等)。最后,一般我們需要 yield 一個 scrapy.Request 即相當於返回一個請求,這個請求可以設置很多參數,比較重要的有 headers(頭部信息),callback(回調函數),meta(傳遞額外信息,默認傳遞的只是response)。比如我們寫 yield scrapy.Request(url,callback=self.parse_url),就是設定回調函數為 parse_url 函數,我們將 response 傳遞給parse_url 進行進一步解析。
還有一點就是,在這里我們將爬取到的數據存入數據庫,python 連接數據庫一般有 MySQLdb 和 pymysql 兩個驅動可以選擇,我一般使用的是前者,但是比較坑爹的是
MySQLdb 好像只支持 32 位系統,反正我用64位的python 裝了好多次都沒成功。所以建議使用 pip install pymysql 來安裝,pymysql 使用純python寫的驅動,用法和 mysqldb差不多。我們需要在piplines.py 中 寫插入數據庫的操作,代碼大概像下面這樣:
1 import pymysql 2 3 class JbzjPipeline(object): 4 def process_item(self, item, spider): 5 url = item['article_url'] # 文章url 6 title = item['article_title'] # 文章標題 7 content = item['article_content'] # 內容 8 # 建立數據庫連接 9 conn = pymysql.connect(host = 'localhost',user = 'root',passwd = 'passwd',db = 'your_db',charset = 'utf8') 10 cursor = conn.cursor() 11 sql = "insert into jbzj VALUES(NULL,%s,%s,%s)" 12 cursor.execute(sql,(url,title,content)) # 執行sql語句 13 cursor.close() 14 conn.commit() # 提交數據庫 15 print(u"成功插入一條數據!") 16 conn.close() # 關閉連接
我們需要覆寫 JbzjPipeline 類的 process_item 方法,這個方法在 yield item 之后會自動調用,需要傳入兩個參數,一個是item(數據字段),一個是spider(哪個爬蟲),我們在這個函數下寫插入數據進入數據庫的操作就可以了。
對了還有 items.py 文件,大概長下面這樣:
class JbzjItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() article_url = scrapy.Field() # 文章鏈接 article_title = scrapy.Field() # 文章標題 article_content = scrapy.Field() # 文章內容
我們采用 name = scrapy.Field()這樣的形式來定義我們需要的字段,類需要繼承自 scrapy.Item,在這里我就簡單定義了文章鏈接、內容和標題三個字段。
總結一下:scrapy 抓取的基本步驟大概就是:從start_urls 中的url開始抓取,默認調用 start_requests ,然后將響應的請求傳給 parse方法,parse方法再傳遞給它的回調函數,以此類推,直到最后一層 yield item,然后 piplines.py 開始處理數據的儲存,當然我說的很簡單,實際處理的過程比這個還要復雜一點,scrapy默認就是開啟多線程的,整個過程不是順序執行,如果想要徹底弄明白scrapy運行的機制,可以去找官方文檔。
最后 給出實際的代碼:
jbzj_spider.py
# -*- coding: utf-8 -*- ''' scrapy 腳本之家爬蟲實例:http://www.jb51.net/article/54323.htm ''' import re import scrapy from ..items import JbzjItem from scrapy.selector import Selector class JbzjSpiderSpider(scrapy.Spider): name = "jbzj_spider" allowed_domains = ["www.jb51.net"] base_url = "http://www.jb51.net" start_urls = [ 'http://www.jb51.net/article/109909.htm', 'http://www.jb51.net/article/110219.htm' ] # def start_requests(self): # yield scrapy.Request(self.start_urls[0],callback=self.parse) def parse(self, response): html = response.body # 網頁源碼 urls_list = re.findall(re.compile(r'<a href="(/article/\d+\.htm)".+?</a>'),html) full_urls_list = [self.base_url + url for url in urls_list] # 完整列表 for url in full_urls_list: yield scrapy.Request(url,callback=self.parse_url) def parse_url(self,response): item = JbzjItem() # 實例化一個item selector = Selector(response) # 構造一個選擇器 title = selector.xpath("//div[@class='title']/h1/text()").extract()[0] # 標題 content = selector.xpath("//div[@id='content']//text()").extract() # 內容 item['article_url'] = response.url item['article_title'] = title item['article_content'] = "".join(content) yield item html = response.body # 網頁源碼 urls_list = re.findall(re.compile(r'<a href="(/article/\d+\.htm)".+?</a>'),html) full_urls_list = [self.base_url + url for url in urls_list] # 完整列表 for url in full_urls_list: yield scrapy.Request(url,callback=self.parse_url2) def parse_url2(self,response): item = JbzjItem() # 實例化一個item selector = Selector(response) # 構造一個選擇器 title = selector.xpath("//div[@class='title']/h1/text()").extract()[0] # 標題 content = selector.xpath("//div[@id='content']//text()").extract() # 內容 item['article_url'] = response.url item['article_title'] = title item['article_content'] = "".join(content) yield item
對了,為了使用piplines.py,我們需要將 settings.py 中的 ITEM_PIPELINES 一項注釋去掉,不然無法使用 piplines。
以上就是 本文的基本內容,后續有時間還會更新 scrapy的其他方面的內容。