,都說標題是文章的靈魂,想了半天沒想到什麼比較有創意的標題,只好百度了一個。啊哈哈哈哈哈哈,朕真是太機智了
這是一篇介紹如何使用python搭建IP池的文章,如果愛卿對此不感興趣,那很抱歉,標題耽誤了你寶貴的時間。
事情的起因是這樣,前段時間我寫了一篇介紹如何爬取小說的blog【python那些事.No2】,在爬取的過程中,發現同一個IP連續只能獲取前幾頁小說內容,原本是想搭建IP池繞過這個限制的,奈何項目上來了新任務,爲了在被關進小黑屋之前把文章發佈出來,就想了一個折中的辦法–延時獲取。
沒想到文章發出來後,竟然還有人評論催更 搭建IP池。朕當時就龍顏大怒,長這麼大朕何時受過這種氣啊。從來都是朕催更那些小說作者,被別人催更還是頭一遭
但是打又打不到,罵又罵不得,咋辦?想了想那還是更吧。
衆所周知,由於python爬蟲這種簡單易學的技術普及之後,爲了網站的穩定運行和網站數據的安全,越來越多的網站對爬蟲做各式各樣的限制和反扒措施。其中,限制一定時間內同一IP的請求次數似乎已經成爲了最常見的手段。
很多時候,使用延時獲取的方式–‘在兩次請求之間sleep一定的時間’ 可以解決網站對爬蟲的限制。可是像朕這種年輕人,想要的現在就要,怎麼辦呢?
既然是對同一IP的限制,那就意味着兩次請求的IP如果不同,此限制就形同虛設。
One way of thinking 去網上買代理IP。搞這個業務的有很多,不過真正哪個服務提供商的有效IP率最高,還需要各方仔細斟酌
Another way of thinking 自己搭建IP池。畢竟對朕這種吃一碗麪都還要猶豫加不加煎蛋的社畜來說,買代理IP這種事情,是萬萬幹不出來的。那麼這個時候,就有必要了解一下如何搭建IP池,以及如何提高IP池的有效IP率
先介紹一下搭建IP池的基本思路:
1.找免費代理IP網站:網上代理IP網站有很多,大多都是免費+收費模式。如西刺代理、89免費代理、快代理等。
2.分析頁面,獲取數據(IP、端口、類型)並存儲(多存於數據庫,方便存取和分析)
3.篩選、過濾:爲了保證IP的有效性,有必要對獲取的免費代理IP進行過濾和篩選,去掉不可用的和重複的
本文以西刺代理的國內高匿代理IP爲例:
地址請在代碼裏查找,或者自行百度
頁面大概長這樣:
先寫一個方法獲取所有頁面url並put入隊,【原理:獲取頁面 下一頁按鈕的href值,並組裝】。
warning:訪問速度別太快,很容易被西刺封IP(經過朕的親自測試,確定西刺官網的封IP機制很靈敏),下同,切記。如果你不幸被封,可以切換網絡繼續(如:將WIFI切換成手機熱點),或者等第二天西刺會將IP解封。
#獲取頁面URL
def get_url(start_url,queue):
while True:
print(start_url)
#生成請求代理信息
headers = random.choice(header_list)
# 獲取頁面信息
response = requests.get(url=start_url, headers=headers)
# 獲取請求狀態碼
code = response.status_code
#將頁面URL入隊
queue.put(start_url)
if code == 200:
html = et.HTML(response.content.decode('utf-8'))
#獲取信息
r = html.xpath('//a[@rel="next"]/@href')
if r:
#拼接下一頁的url
start_url = 'https://www.xicidaili.com/' + r[0]
else:
#跳出循環,頁面url獲取完成
break
else:
print(code)
time.sleep(2)
print('Get url complete')
然後寫一個方法獲取頁面中(頁面地址從隊列get)我們所需要的那些信息,包括IP、類型、端口。【分析頁面可得,我們所需要的信息在一個非常整齊的table裏面,只需要取相應的td就行】
#獲取頁面IP信息
def get_info(queue):
while not queue.empty():
#休息一下
time.sleep(3)
#生成請求代理信息
headers = random.choice(header_list)
#從隊列獲取頁面url
page_url = queue.get()
queue.task_done()
print(page_url)
# 獲取頁面信息
response = requests.get(url=page_url, headers=headers)
# 獲取請求狀態碼
code = response.status_code
#頁面所有代理IP信息存儲
data_map = []
if code == 200:
html = et.HTML(response.content.decode('utf-8'))
#獲取信息
r = html.xpath('//tr[position()>1]')
for i in r:
#每一個tr中的信息
data = {
'ip' : ''.join(i.xpath(".//td[2]//text()")),
'port' : ''.join(i.xpath(".//td[3]//text()")),
'type' : ''.join(i.xpath(".//td[6]//text()"))
}
#彙總
data_map.append(data)
#【一種更優雅的獲取表格數據方式:pandas】
#存儲
db_save(data_map)
else:
print(code)
print('It is NUll')
當然還需要一個存儲方法,存入數據庫是爲了方便分析、驗證和調用(你當然也可以存入文件)
#插入數據庫
def db_save(data):
conn = mysql.connector.connect(user='root',password='root',database='test')
for k in data:
try:
cursor = conn.cursor()
cursor.execute('insert into ip_pool (ip, port, type) values (%s, %s, %s)', [k['ip'], k['port'], k['type']])
conn.commit()
print('【OK】數據插入成功,IP:',k['ip'])
except Exception as e:
print('【ERROR】數據插入失敗:',e)
finally:
cursor.close()
conn.close()
既然要存數據庫,那肯定要建個數據表(此處提供一個數據表簡單demo):
# Host: localhost (Version: 5.7.26)
# Date: 2020-01-19 13:47:45
# Generator: MySQL-Front 5.3 (Build 4.234)
/*!40101 SET NAMES utf8 */;
#
# Structure for table "ip_pool"
#
DROP TABLE IF EXISTS `ip_pool`;
CREATE TABLE `ip_pool` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`ip` char(16) DEFAULT NULL COMMENT 'ip',
`port` char(5) DEFAULT NULL COMMENT '端口號',
`type` char(5) DEFAULT NULL COMMENT '類型',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`Id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
整理整理思路,得如下代碼:
#IP池搭建 西刺代理
import requests
import random
from lxml import etree as et
from queue import Queue #導入queue模塊
import time #導入time模塊
import mysql.connector #導入數據庫模塊
import threading #導入threading模塊
#常用請求代理
header_list = [
{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36"},
{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3676.400 QQBrowser/10.4.3469.400"},
{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36"},
{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362"}
]
#插入數據庫
def db_save(data):
conn = mysql.connector.connect(user='root',password='root',database='test')
for k in data:
try:
cursor = conn.cursor()
cursor.execute('insert into ip_pool (ip, port, type) values (%s, %s, %s)', [k['ip'], k['port'], k['type']])
conn.commit()
print('【OK】數據插入成功,IP:',k['ip'])
except Exception as e:
print('【ERROR】數據插入失敗:',e)
finally:
cursor.close()
conn.close()
#獲取頁面URL
def get_url(start_url,queue):
while True:
print(start_url)
#生成請求代理信息
headers = random.choice(header_list)
# 獲取頁面信息
response = requests.get(url=start_url, headers=headers)
# 獲取請求狀態碼
code = response.status_code
#將頁面URL入隊
queue.put(start_url)
if code == 200:
html = et.HTML(response.content.decode('utf-8'))
#獲取信息
r = html.xpath('//a[@rel="next"]/@href')
if r:
#拼接下一頁的url
start_url = 'https://www.xicidaili.com/' + r[0]
else:
#跳出循環,頁面url獲取完成
break
else:
print(code)
time.sleep(2)
print('Get url complete')
#獲取頁面IP信息
def get_info(queue):
while not queue.empty():
#休息一下
time.sleep(3)
#生成請求代理信息
headers = random.choice(header_list)
#從隊列獲取頁面url
page_url = queue.get()
queue.task_done()
print(page_url)
# 獲取頁面信息
response = requests.get(url=page_url, headers=headers)
# 獲取請求狀態碼
code = response.status_code
#頁面所有代理IP信息存儲
data_map = []
if code == 200:
html = et.HTML(response.content.decode('utf-8'))
#獲取信息
r = html.xpath('//tr[position()>1]')
for i in r:
#每一個tr中的信息
data = {
'ip' : ''.join(i.xpath(".//td[2]//text()")),
'port' : ''.join(i.xpath(".//td[3]//text()")),
'type' : ''.join(i.xpath(".//td[6]//text()"))
}
#彙總
data_map.append(data)
#【一種更優雅的獲取表格數據方式:pandas】
#存儲
db_save(data_map)
else:
print(code)
print('It is NUll')
# 主函數
if __name__ == "__main__":
#開始頁URL
start_url = 'https://www.xicidaili.com/nn/'
#用Queue構造一個大小爲1000的線程安全的先進先出隊列
page_url_queue = Queue(maxsize=1000)
#創建一個線程抓取頁面url
t1 = threading.Thread(target=get_url, args=(start_url,page_url_queue))
#開始線程
t1.start()
time.sleep(2)
#創建一個線程分析頁面信息,並存儲
t2 = threading.Thread(target=get_info, args=(page_url_queue,))
t2.start()
#結束線程
t1.join()
t2.join()
print('the end!')
一運行:
哎喲,我去。BUG?不存在的
打開數據庫看看:
呵,整整齊齊
當然,免費代理IP大部分都是無效的。
所以,需要將獲得的IP再進行有效性校驗,刪掉不可用的,保證我們在需要的時候取到的IP可用。
這裏提供幾個思路:
1.在插入數據庫之前,先檢查一下該代理IP是否可用,如果不可用,則直接下一個
2.由於有的代理IP有效期很短,所以需要定時檢測數據表中代理IP的有效性,去掉不可用的
3.在使用之前,從數據庫中取出的IP,先判斷該IP的有效性。
自建IP池完整代碼,git地址:~~在不久的將來,此處將會有一個git地址
眼淚不是答案,拼搏纔是選擇。只有回不了的過去,沒有到不了的明天。
所有的不甘,都是因爲還心存夢想,在你放棄之前,好好拼一把,只怕心老,不怕路長。
The end !