Python爬蟲-Redis
前言
作為跟MongoDB同樣NoSQL陣營的Redis,也具有類似的“直爽快”特性。它本身讀取速度快,又提供豐富的數據結構,避免程序員重復造輪子。大名鼎鼎的分布式scrapy也是基於redis,所以趕緊了解一下唄!
啟動服務器
建議指定配置文件的方式啟動,我的配置文件所在路徑: /etc/redis/redis-server.conf
就不知道你們的是不是啦
啟動方式:redis-server /etc/redis/redis-server.conf
啟動客戶端
- 進入命令行:
redis-cli
- 退出客戶端:
exit
數據操作
redis是key-value的數據,key的類型是字符串,value類型可以是:string,hash,list,set,zset
string
最基本類型,最大存儲512M數據,可存儲任何數據:數字,圖片,序列化對象等
- 設置鍵值:
set key value
- 也可以一次設置多個數據:
mset key1 value1 key2 value2 key3 value3...
- 獲取鍵值:
get key
;也可以一次獲取多個鍵值:mget key1 key2 key3...
如果不存在,返回nil - 甚至還能追加鍵值:
append key value
- 也可以獲取鍵值長度(有點像C語法哇):
strlen key
- 也可以在設置鍵值的時候設置過期時間:
setex key seconds value
- 運算(鍵值要求為數值):
incr key
鍵值+1;incrby key increment
鍵值指定+increment ;相對應的就有decr key
鍵值-1,decrby key increment
指定-increment
鍵命令
(不僅僅針對於string類型,其他value類型的key都適用)
- 查找鍵:
keys pattern
(支持正則),所以查看全部鍵可以是keys *
- 查看鍵是否存在:
exists key
,存在返回1,不存在返回0
也可以一次查詢多個鍵:exists key1 key2 ...
,返回值為總和
- 查看鍵的類型:
type key
- 刪除鍵:
del key
,成功返回1,失敗或鍵不存在返回0;或者刪除多個鍵:del key1 key2 ...
,返回值為總和,即便鍵不存在,亦不會報錯
- 設置過期時間:
expire key seconds
;查看鍵的剩余存活時間:ttl key
expire操作后返回1表示設置成功,返回0表示設置失敗或是鍵不存在,設置時間單位秒;ttl操作后返回剩余存貨時間,如果返回-2表示鍵不存在,如果返回-1代表永久存在 - 當然,也能清除過期時間:
persist key
如果persit操作之后返回1表示操作成功;如果返回0,鍵不存在或鍵本身就是永久
hash
用於存儲對象,對象格式為鍵值對
1. hset key field value / hmset key field1 value1 field2 value2 ...
,如何理解”對象”呢?即:一個人,這就是一個對象,有名字,年齡,性別等
2. hget key field / hmget key field1 field2 ...
3. 獲取指定key的所有字段和值: hgetall key
4. 獲取指定key的所有字段:hkeys key
5. 獲取指定key的所有值:hvals key
6. 獲取指定key的字段個數:hlen key
7. 判斷key的字段是否存在:hexists key field
,存在返回1,不存在返回0
8. 刪除字段及對應值:hdel key field / hdel key field1 field2 ...
9. 刪除key:del key
10. 獲取值的字符串長度:hstrlen key field
我查了使用文檔,的確存在這個hstrlen命令,用Tab命令提示也能自動彈出來,但是——
哈哈,布吉島布吉島,先占位以后填坑吧
list
列表的元素類型是string,按照插入順序排序,可列表的頭或尾添加元素
- 從頭/尾部插入:
lpush/rpush key value
,每次返回的值是列表中的元素個數
- 在一個元素的前/后插入元素:
linsert key before/after pivot value
,這里的支點(pivot)就是原列表中的元素,value則是需要新添加的元素
失敗則返回-1 - 移除並獲得值:
lpop/rpop key
,這里可以用python的list類型的pop方法來理解
- 利用索引獲取元素:
lindex key index
- 獲取key的長度(也就是列表中的元素個數):
llen key
- 修改列表中的元素值:
lset key index value
,指定元素在列表中的索引(index),value是修改后的內容。==如果index值上並不存在元素,報索引錯誤== - 返回指定范圍的元素:
lrange key startIndex stopIndex
超出索引范圍不報錯 - 裁剪列表:
ltrim key startIndex stopIndex
set
無序集合,元素類型string,元素具有唯一性,不重復
- 添加元素:
sadd key member
;或者一次添加多個:sadd key member1 member2 ...
,如果添加的元素已存在,返回0
- 移除元素:
srem key member / srem key member1 member2 ...
- 獲取所有元素:
smembers key
- 獲取集合元素個數:
scard key
- 求多個集合的交集:
sinter key1 key2 ...
- 求集合與其他集合的差集:
sdiff key1 key2 ...
- 求多個集合的合集:
sunion key1 kye2 ...
- 判斷元素是否在集合中:
sismember key member
,存在返回1,不存在返回0
zset
有序集合,唯一性,每個元素都會關聯到一個double類型的score,表示權重,根據權重對元素排序,元素的score可以相同
zadd key score member
;zadd key socre1 member1 score2 member2 ...
zrem key member/zrem key member1 member2 ...
zrange key start stop
zcard key
- 統計score值在min與max的個數:
zcount key min max
- 返回member的score值:
zscore key member
發布訂閱
Redis 發布訂閱(pub/sub)是一種消息通信模式:發送者(pub)發送消息,訂閱者(sub)接收消息。
- 消息格式:
a.subscribe 頻道名1 [頻道名2 ...]
訂閱
b.unsubscribe 頻道名1 [頻道名2 ...]
退訂
c.message之后顯示頻道,再之后顯示正文
d.publish 頻道 消息
發布
發布消息
接受消息
主從配置
每個主都可以設置許多的從,每個從又可以設置許多的從;通過設置主從,搭建分布式,如scrapy-redis分布式爬蟲
- 對主機修改配置文件:
bind 主機Ip
- 對從機修改配置文件:
bind 從機Ip
slaveof 主機Ip port
與python交互
- 安裝包redis,導入:
import redis
- 連接redis:
r = redis.StrictRedis(host="hostname", prot=6379)
- 使用方法1:根據數據類型,使用對應方法,如:
r.set("name", "kaihui") / r.get("name")
- 使用方法2:
p = r.pipeline()
p.set(...)
p.get(...)
p.execute()
==法2緩沖多條命令,然后一次性執行,減少服務器-客戶端之間TCP數據庫包,從而提高效率==
之前在redis客戶端操作的命令,都很好的封裝到了連接數據庫的對象里,可以直接使用,如:r.hset() r.sadd()…
利用redis實戰
之前抓取過電影排行榜,句子迷,QQ空間,音樂熱評等等,其實大多操作類似,今天想換別的方向(其實操作也類似)。
眾所周知,爬蟲是容易封ip的,為了應對如此強硬的反扒措施,有了利用代理ip爬取信息的方法。既然有了需求,當然就有了市場,我也沒非要不可的數據需要爬取,便不至於花錢購買代理。
西刺網站提供免費代理,既然有“免費”二字,效果大家就應該心知肚明的。或許人公司提供的效果不至於那么差,卻奈何全國爬蟲都想“好好”利用。
分析網頁一如既往,這個網頁毫無難度,requests發起get請求,利用xpath解析響應,提取ip和端口號,再存入redis。稍微不同的是,我們拿到的代理ip能不能用呢?這里需要做一個測試,我寫了一個filter_ip()函數進行過濾,主要思想是利用代理去訪問百度,如果返回狀態碼是200,那么OK,存起來;否則丟棄
def filter_ip(proxyData):
# 剔除https類型的
if proxyData["type"].upper() == "HTTPS":
return
del proxyData["type"]
# 構造requests模塊ip代理的參數結構
proxyData = {
"http":"http://"+proxyData["ip"]+":"+proxyData["port"]
}
try:
response = requests.get(url="http://www.baidu.com/", proxies=proxyData)
response.raise_for_status()
except:
print(f"{proxyData}不可用")
return None
# 若可用,存入redis
to_redis(proxyData)
由於構建ip池只是為了更好的輔助爬蟲,所以效率方面要求嚴格,為此我利用了多進程+多線程來達到目的
# 設置進程池
pool = Pool(10)
for item in parse_html(html):
pool.apply_async(set_threading, (item, ))
pool.close()
pool.join()
def set_threading(item):
# 設置線程函數
t = Thread(target=filter_ip, args=(item, ))
t.start()
t.join()
整體邏輯如下
反思
我抓取了代理網頁前三頁,然而僅9個寫入數據庫,其免費可想而知
最初寫的單進程+單線程,運行速度極慢,才想到多進程+多線程。多番調試,速度提升了6倍。然而也不過對300個ip處理而已,竟需要200s上下。與GitHub上最高star的開源項目差之千里
想起一晚趴在床上看《愛你就像愛生命》,有多少人知道王小波也是個程序員呢?他在給朋友曉陽的信中這樣寫道:我的圖像部分也是匯編寫的,反復優化,也達不到他的水平,不得不承認技不如人。
路漫漫其修遠兮呢!