人生苦短,我用 Python
前文傳送門:
小白學(xué) Python 爬蟲(2):前置準(zhǔn)備(一)基本類庫的安裝
小白學(xué) Python 爬蟲(3):前置準(zhǔn)備(二)Linux基礎(chǔ)入門
小白學(xué) Python 爬蟲(4):前置準(zhǔn)備(三)Docker基礎(chǔ)入門
小白學(xué) Python 爬蟲(5):前置準(zhǔn)備(四)數(shù)據(jù)庫基礎(chǔ)
小白學(xué) Python 爬蟲(6):前置準(zhǔn)備(五)爬蟲框架的安裝
小白學(xué) Python 爬蟲(7):HTTP 基礎(chǔ)
小白學(xué) Python 爬蟲(8):網(wǎng)頁基礎(chǔ)
小白學(xué) Python 爬蟲(9):爬蟲基礎(chǔ)
小白學(xué) Python 爬蟲(10):Session 和 Cookies
小白學(xué) Python 爬蟲(11):urllib 基礎(chǔ)使用(一)
小白學(xué) Python 爬蟲(12):urllib 基礎(chǔ)使用(二)
小白學(xué) Python 爬蟲(13):urllib 基礎(chǔ)使用(三)
小白學(xué) Python 爬蟲(14):urllib 基礎(chǔ)使用(四)
小白學(xué) Python 爬蟲(15):urllib 基礎(chǔ)使用(五)
小白學(xué) Python 爬蟲(16):urllib 實(shí)戰(zhàn)之爬取妹子圖
小白學(xué) Python 爬蟲(17):Requests 基礎(chǔ)使用
小白學(xué) Python 爬蟲(18):Requests 進(jìn)階操作
小白學(xué) Python 爬蟲(19):Xpath 基操
小白學(xué) Python 爬蟲(20):Xpath 進(jìn)階
小白學(xué) Python 爬蟲(21):解析庫 Beautiful Soup(上)
小白學(xué) Python 爬蟲(22):解析庫 Beautiful Soup(下)
小白學(xué) Python 爬蟲(23):解析庫 pyquery 入門
小白學(xué) Python 爬蟲(24):2019 豆瓣電影排行
小白學(xué) Python 爬蟲(26):為啥買不起上海二手房你都買不起
小白學(xué) Python 爬蟲(27):自動化測試框架 Selenium 從入門到放棄(上)
小白學(xué) Python 爬蟲(28):自動化測試框架 Selenium 從入門到放棄(下)
小白學(xué) Python 爬蟲(29):Selenium 獲取某大型電商網(wǎng)站商品信息
小白學(xué) Python 爬蟲(30):代理基礎(chǔ)
前面的代理如果有同學(xué)動手實(shí)踐過,就會發(fā)現(xiàn)一個(gè)問題,現(xiàn)在網(wǎng)上的免費(fèi)代理簡直太坑啦?。。?/p>
經(jīng)常一屏幕好多的代理試下來,沒有幾個(gè)能用的。
當(dāng)然,免費(fèi)的代理嘛,連通率低、延遲高是正常的,人家畢竟是免費(fèi)的。
但是這件事兒有沒有解決方案呢?
這么天資聰穎的小編肯定是想到了辦法了呀。
先來屢屢這件事兒,其實(shí)我們要的不是連通率高,而是我們在使用的時(shí)候,能每次都用到能用的代理。
這件事兒要么我們每次在用的時(shí)候自己手動去試,要么~~~
我們可以寫程序讓程序自己去尋找合適的代理嘛~~~
其實(shí)這一步就是把需要我們手動做的事情變成了程序自動去完成。
先想一下這個(gè)代理池最少需要有哪些功能:
自動獲取代理
定時(shí)清除不能用的代理
這兩個(gè)是我們的核心訴求,最少要有這兩個(gè)功能,不然這個(gè)代理池也沒有存在的價(jià)值了。
根據(jù)上面兩個(gè)功能,我們來拆解程序的模塊,小編這里定義了三個(gè)模塊,獲取模塊(獲取代理)、存儲模塊(數(shù)據(jù)庫存儲代理)、檢測模塊(定時(shí)檢查代理的可用性)。
那么它們?nèi)叩年P(guān)系就是這樣的:
這里的存儲模塊我們使用 Mysql ,這與存儲模塊為什么選 Mysql ,因?yàn)?Mysql 有表結(jié)構(gòu),給各位同學(xué)展示起來比較清晰,如果需要用于生產(chǎn)環(huán)境的話,建議是用 Redis ,提高效率。
首先還是貼一下數(shù)據(jù)庫的表結(jié)構(gòu),之前有同學(xué)留言問過小編表結(jié)構(gòu)的事情,是小編偷懶沒有貼。
本次設(shè)計(jì)使用的還是單表模式,一張表走天下就是小編本人了。
至于字段的含義小編就不介紹了,后面的注釋已經(jīng)寫得比較清楚了。
對于存儲模塊來講,主要的功能是要將我們獲取到的代理保存起來,上面的 Mysql 數(shù)據(jù)庫是存儲模塊的一部分。
基于 OOP 的思想,我們本次寫一個(gè)類 MysqlClient 將所有對于 Mysql 的操作封裝起來,其他模塊需要和數(shù)據(jù)庫產(chǎn)生交互的時(shí)候只需要調(diào)用我們在 MysqlClient 中封裝好的方法即可。
示例代碼如下:
MYSQL_HOST = 'localhost' MYSQL_PORT = 3306 MYSQL_USER = 'root' MYSQL_PASSWORD = 'password' MYSQL_DB ='test' MYSQL_CHARSET = 'utf8mb4' import pymysql import uuid class MysqlClient(object): def __init__(self, host=MYSQL_HOST, port=MYSQL_PORT, user=MYSQL_USER, password=MYSQL_PASSWORD, database=MYSQL_DB, charset=MYSQL_CHARSET): """ 初始化 mysql 連接 :param host: mysql 地址 :param port: mysql 端口 :param user: mysql 用戶 :param password: mysql 密碼 :param database: mysql scheme :param charset: 使用的字符集 """ self.conn = pymysql.connect( host = host, port = port, user = user, password = password, database = database, charset = charset ) def add_proxy(self, proxy): """ 新增代理 :param proxy: 代理字典 :return: """ sql = 'INSERT INTO `proxy_pool` VALUES (%(id)s, %(scheme)s, %(ip)s, %(port)s, %(status)s, %(response_time)s, now(), null )' data = { "id": str(uuid.uuid1()), "scheme": proxy['scheme'], "ip": proxy['ip'], "port": proxy['port'], "status": proxy['status'], "response_time": proxy['response_time'], } self.conn.cursor().execute(sql, data) self.conn.commit() def find_all(self): """ 獲取所有可用代理 :return: """ sql = 'SELECT * FROM proxy_pool WHERE status = "1" ORDER BY update_date ASC ' cursor = self.conn.cursor() cursor.execute(sql) res = cursor.fetchall() cursor.close() self.conn.commit() return res def update_proxy(self, proxy): """ 更新代理信息 :param proxy: 需要更新的代理 :return: """ sql = 'UPDATE proxy_pool SET scheme = %(scheme)s, ip = %(ip)s, port = %(port)s, status = %(status)s, response_time = %(response_time)s, update_date = now() WHERE id = %(id)s ' data = { "id": proxy['id'], "scheme": proxy['scheme'], "ip": proxy['ip'], "port": proxy['port'], "status": proxy['status'], "response_time": proxy['response_time'], } self.conn.cursor().execute(sql, data) self.conn.commit()
在這個(gè)類中,我們首先定義了一些常量,都是和數(shù)據(jù)庫連接有關(guān)的常量,如 MYSQL_HOST 數(shù)據(jù)庫地址、 MYSQL_PORT 數(shù)據(jù)庫端口、 MYSQL_USER 數(shù)據(jù)庫用戶名、 MYSQL_PASSWORD 數(shù)據(jù)庫密碼、 MYSQL_DB 數(shù)據(jù)庫的 scheme 、 MYSQL_CHARSET 字符集。
接下來定義了一個(gè) MysqlClient 類,定義了一些方法用以執(zhí)行數(shù)據(jù)庫的相關(guān)操作。
init(): 初始化方法,在初始化 MysqlClient 這個(gè)類時(shí),同時(shí)初始化了 Mysql 數(shù)據(jù)庫的鏈接信息,獲得了數(shù)據(jù)庫連接 connection 。
add_proxy():向數(shù)據(jù)庫中添加代理,并添加相關(guān)信息,包括代理響應(yīng)延時(shí)和健康狀況。
find_all():獲取所有數(shù)據(jù)庫可用代理,并根據(jù)更新時(shí)間正序排布,主要用于后續(xù)代理檢查。
update_proxy():更新代理信息,主要用戶檢查模塊檢查完代理后更新代理信息,根據(jù)取出當(dāng)前代理的主鍵 id 進(jìn)行更新。
獲取模塊相對比較簡單,主要功能就是從各個(gè)免費(fèi)代理網(wǎng)站上將我們所需要的代理信息抓取下來。示例如下:
import requests from pyquery import PyQuery from MysqlClient import MysqlClient from VerifyProxy import VerifyProxy class CrawlProxy(object): def __init__(self): self.mysql = MysqlClient() self.verify = VerifyProxy() def get_page(self, url, charset): response = requests.get(url) response.encoding = charset return response.text def crawl_ip3366(self, page_num = 3): """ 獲取代理 ip3366 :param page_num: :return: """ start_url = 'http://www.ip3366.net/?stype=1&page={}' urls = [start_url.format(page) for page in range(1, page_num + 1)] for url in urls: print('crawl:', url) html = self.get_page(url, 'gb2312') if html: d = PyQuery(html) trs = d('.table-bordered tbody tr').items() for tr in trs: scheme = tr.find('td:nth-child(4)').text().lower() ip = tr.find('td:nth-child(1)').text() port = tr.find('td:nth-child(2)').text() verify_result = self.verify.verify_proxy(scheme, ip, port) if verify_result["status"] == '1': proxy = { "scheme": scheme, "ip": ip, "port": port, "status": verify_result["status"], "response_time": verify_result["response_time"], } # 存入數(shù)據(jù)庫 self.mysql.add_proxy(proxy) print('代理', ip, '連通測試已通過,已保存 Mysql') else: print('代理', ip, '連通測試未通過') if __name__ == '__main__': CrawlProxy().crawl_ip3366()
小編這里出于示例只演示了從 ip3366 上抓取免費(fèi)代理,并且在抓取到代理后,調(diào)用檢查模塊的檢查方法對當(dāng)前的代理進(jìn)行連通性檢查,如果連通性測試未通過則不會寫入數(shù)據(jù)庫中。
檢查模塊相對也會簡單一些,功能是從數(shù)據(jù)庫中取出所有可以用的代理,進(jìn)行輪詢檢查,看看是不是有代理是連不通的,如果連不通則修改連通性標(biāo)記位,將此代理標(biāo)記為不可用。示例代碼如下:
import requests from MysqlClient import MysqlClient class VerifyProxy(object): def __init__(self): self.mysql = MysqlClient() def verify_proxy(self, scheme, ip, port): """ 使用百度測試代理的連通性,并返回響應(yīng)時(shí)長(單位:ms) :param scheme: :param ip: :param port: :return: """ proxies = { scheme: scheme + '://' + ip + ':' + port + '/' } response_time = 0 status = '0' try: response = requests.get(scheme + '://www.baidu.com/get', proxies=proxies) if response.ok: response_time = round(response.elapsed.total_seconds() * 1000) status = '1' else: response_time = 0 status = '0' except: pass return {"response_time" : response_time, "status" : status} def verify_all(self): """ 驗(yàn)證住方法,從數(shù)據(jù)庫中獲取所有代理進(jìn)行驗(yàn)證 :return: """ results = self.mysql.find_all() for result in results: res = self.verify_proxy(result[1], result[2], result[3]) proxy = { "id": result[0], "scheme": result[1], "ip": result[2], "port": result[3], "status": res["status"], "response_time": res["response_time"], } self.mysql.update_proxy(proxy) print('代理驗(yàn)證成功') if __name__ == '__main__': VerifyProxy().verify_all()
小編這里使用的是度娘進(jìn)行連通性測試,如果各位同學(xué)有特殊的需要,可以使用特定的網(wǎng)站進(jìn)行連通性測試。
本篇的內(nèi)容到這里就結(jié)束了,不過有一點(diǎn)要說明,本篇的示例內(nèi)容只能作為 DEMO 來進(jìn)行測試使用,對于一個(gè)連接池來講,還有很多不完善的地方。
例如檢測模塊應(yīng)該是定時(shí)啟動,自行檢測,現(xiàn)在是靠人手動啟動,不過這個(gè)可以使用各種系統(tǒng)上的定時(shí)任務(wù)來解決。
還有,現(xiàn)在要獲取連接信息只能自己打開數(shù)據(jù)庫從中 Copy ,這里其實(shí)還可以加一個(gè) API 模塊,寫成一個(gè)接口,供其他有需要使用代理的系統(tǒng)進(jìn)行調(diào)用。
獲取模塊現(xiàn)在小編也只是簡單的有一個(gè)網(wǎng)站寫一個(gè)方法,其實(shí)可以使用 Python 高級用法,獲取到類中所有的方法名,然后調(diào)用所需要的方法。
總之,這個(gè) DEMO 非常不完善,等小編下次有空的時(shí)候完善下,到時(shí)候還可以再來一個(gè)推送。
本系列的所有代碼小編都會放在代碼管理倉庫 Github 和 Gitee 上,方便大家取用。
聯(lián)系客服