banner
soapffz

soapffz

github
steam
bilibili
douban

多线程爬取西刺高匿代理并验证可用性

前言#

为啥要用代理? 不管是爆破还是爬虫,经常都会遇见 ip 被封掉的情况,此时,自建一个可靠的代理池就显得很有必要了

代理有哪些分类 以代理的匿名度来分,可以分为透明代理、匿名代理和高度匿名代理

  • 使用透明代理,对方服务器可以知道你使用了代理,并且也知道你的真实 IP。
  • 使用匿名代理,对方服务器可以知道你使用了代理,但不知道你的真实 IP。
  • 使用高匿名代理,对方服务器不知道你使用了代理,更不知道你的真实 IP。

此篇文章就是爬取国内比较出名的西刺代理 (还有一个快代理也比较出名,爬取步骤和这个差不多) 的高匿代理并且验证可用性

思路#

获取信息#

首先,我们需要用 requests 库把网页爬下来,然后查看我们需要的信息在哪个标签页里

可以看到每一个代理的所有信息都包含在一个 tr 标签中,而每一个信息又都是放在一个 td 标签中的:

image

那么我们就可以使用 lxml 库来解析,用 xpath 来寻找标签,简单示例代码如下:

import requests
from lxml import etree

url = 'https://www.xicidaili.com/nn/1'  # 需要爬取的url
req = requests.get(url)  # 用requests请求
content = req.content.decode("utf-8")  # 将内容解码
tree = etree.HTML(content)  # 解析html文件
tr_nodes = tree.xpath('.//table[@id="ip_list"]/tr')[1:]  # 使用xpath解析所需要的标签

用 xpath 获得指定标签页的内容可以参考这篇文章:https://www.cnblogs.com/lei0213/p/7506130.html

在获得每个端口 ip、端口、类型、速度、连接时间等信息之后,我们可以进行筛选:

  • 将 http、https 类型的代理 ip 分类
  • 将速度及连接时间不理想的 ip 过滤掉

验证有效性#

之后,我们可以将代理 ip 拿去验证

验证方法的话,网上大部分是再将代理 ip 加载请求头里面拿去请求某个网站,比如百度

如果返回状态码是 200 的话,则此代理 ip 有效

但是这样太过于依赖网络并且效率也不高,看到一篇文章是用每个 ip 用 telnet 去连接一下,如果能连接上,则是有效的

这部分主要的一个参数就是超时时间,也就是等待时间,建议设置为 10 秒

用 telnet 验证代理 ip 有效性的简单示例如下:

import telnetlib

def verifyProxy(ip, port):
try:
telnetlib.Telnet(ip, port, timeout=5) # 用 telnet 连接一下,能连通说明代理可用
except:
pass
else:
print("此代理已验证可用:{}".format(proxies))

if **name** == "**main**":
verifyProxy("113.13.177.80", "9999")

用多线程优化#

在获取信息,验证有效性都可行之后,发现效率太低了,主要耗费时间的地方有这些:

  • 请求多页耗费时间
  • 验证代理需要有一个超时等待时间,后面的队伍都需要等待

那么我们可以使用 multiprocessing 库中的 Pool 线程池配合 apply_async () 这个方法使用

这个线程池可以配合 apply () 方法使用,但是 apply () 是阻塞的。

首先主进程开始运行,碰到子进程,操作系统切换到子进程,等待子进程运行结束后,在切换到另外一个子进程,直到所有子进程运行完毕。然后在切换到主进程,运行剩余的部分。这样跟单进程串行执行没什么区别。

apply_async 是异步非阻塞的。

即不用等待当前进程执行完毕,随时根据系统调度来进行进程切换。首先主进程开始运行,碰到子进程后,主进程仍可以先运行,等到操作系统进行进程切换的时候,在交给子进程运行。可以做到不等待子进程执行完毕,主进程就已经执行完毕,并退出程序。

由于我们是获取到代理、验证有效性就行,不要求顺序,不需要像爬取小说那样爬取之后需要按照章节顺序存入文件

所以我们这里使用 apply_async 是最合适的。

multiprocessing 库中的 Pool 线程池配合 apply_async () 这个方法的简单示例代码如下:

from multiprocessing import Pool

def example(proxies):
if type(proxies) == 'http':
with open("1.txt", 'a') as f:
f.write(proxies+"\n")
else:
with open("2.txt", 'a') as f:
f.write(proxies+"\n")

if **name** == "**main**":
proxy = []
with open("tmp.txt", 'r') as f:
proxy = f.read().splitlines()
pool = Pool() # 新建一个线程池
for line in proxy:
pool.apply_async(target=example, args=(line,)) # target 这个参数名可以省略,建议在最后一个参数后面加上一个,
pool.close() # 创建进程池之后必须关闭
pool.join() # 加入阻塞队列,则要上面的代码执行完毕之后,才会执行后面的代码

具体代码实现#

关键地方都已做了注释:

#!/usr/bin/python

# -_- coding: utf-8 -_-

'''
@author: soapffz
@fucntion: 多线程爬取西刺高匿代理并验证可用性
@time: 2019-01-21
'''

import requests
from lxml import etree
import re
import telnetlib
import threading
from multiprocessing import Pool
import os
import timeit

path = os.path.join(os.path.expanduser("~")+"\\") # 爬下来未验证的的代理先放在当前用户根目录下
http_tmp_path = os.path.join(path+"http_tmp.txt")
https_tmp_path = os.path.join(path+"https_tmp.txt")

# 验证过的代理就放在当前用户桌面

http_proxy_path = os.path.join(path+"Desktop"+"\\"+"http_proxy.txt")
https_proxy_path = os.path.join(path+"Desktop"+"\\"+"https_proxy.txt")
http_proxy = []
https_proxy = []

def get_nn_proxy(page_num):
url = "https://www.xicidaili.com/nn/{}".format(page_num)
headers = {
"User-Agent": 'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0'} # 本来还测试使用随机 User-Agent 头,后来发现没用,西刺封的是 ip
req = requests.get(url, headers=headers)
print("正在爬取第{}页的内容...".format(page_num))
content = req.content.decode("utf-8")
tree = etree.HTML(content) # 用 xpath 获得总的 ip_list
tr_nodes = tree.xpath('.//table[@id="ip_list"]/tr')[1:]
for tr_node in tr_nodes:
td_nodes = tr_node.xpath('./td') # 用 xpath 获得单个 ip 的标签
speed = int(
re.split(r":|%", td_nodes[6].xpath('./div/div/@style')[0])[1]) # 获得速度的值
conn_time = int(
re.split(r":|%", td_nodes[7].xpath('./div/div/@style')[0])[1]) # 获得连接时间的值
if(speed <= 95 | conn_time <= 95): # 如果速度和连接时间都不理想,就跳过这个代理
continue
ip = td_nodes[1].text
port = td_nodes[2].text
proxy_type = td_nodes[4].text
ip_type = td_nodes[5].text.lower()
proxy = "{}:{}".format(ip, port)
if ip_type == 'http':
with open(http_tmp_path, 'a') as f:
f.write("http://{}".format(proxy)+"\n") # 获得爬下来的 http 代理并存在临时文件中
else:
with open(https_tmp_path, 'a') as f: # 获得爬下来的 https 代理并存在临时文件中
f.write("https://{}".format(proxy)+"\n")

def verifyProxy(proxies):
ree = re.split(r'//|:', proxies)
ip_type = ree[0]
ip = ree[2]
port = ree[3]
try:
telnetlib.Telnet(ip, port, timeout=5) # 用 telnet 连接一下,能连通说明代理可用
except:
pass
else:
print("此代理已验证可用:{}".format(proxies))
if ip_type == 'http':
with open(http_proxy_path, 'a') as f:
f.write(proxies+"\n")
else:
with open(https_proxy_path, 'a') as f:
f.write(proxies+"\n")

def clear_cache(path):
if os.path.exists(path):
os.remove(path)

if **name** == "**main**":
start_time = timeit.default_timer()
clear_cache(http_tmp_path)
clear_cache(https_tmp_path)
clear_cache(http_proxy_path)
clear_cache(https_proxy_path)
pool = Pool()
for i in range(1, 11): # 这里爬取的是 1 到 10 页的高匿代理,自行根据需要修改参数
pool.apply_async(get_nn_proxy, args=(i,))
pool.close()
pool.join()
if not os.path.exists(http_tmp_path): # 如果 ip 被 ban 掉,则获取到的每一页代理都是空列表,不会生成文件
print("你的 ip 已经被西刺 ban 掉了,请使用 ipconfig /release 之后使用 ipconfig /renew 换 ip 或者挂 vpn!")
os.\_exit(0)
with open(http_tmp_path, 'r') as f:
http_proxy = f.read().splitlines()
Unhttp_proxy_num = len(http_proxy)
with open(https_tmp_path, 'r') as f:
https_proxy = f.read().splitlines()
Unhttps_proxy_num = len(https_proxy)
pool2 = Pool()
pool3 = Pool()
for proxies in http_proxy:
pool2.apply_async(verifyProxy, args=(proxies,))
for proxies in https_proxy:
pool3.apply_async(verifyProxy, args=(proxies,))
pool2.close()
pool3.close()
pool2.join()
pool3.join()
http_proxy.clear()
https_proxy.clear()
with open(http_proxy_path, 'r') as f:
http_proxy = f.read().splitlines()
http_proxy_num = len(http_proxy)
with open(https_proxy_path, 'r') as f:
https_proxy = f.read().splitlines()
https_proxy_num = len(https_proxy)
print(http_proxy)
print(https_proxy)
print("已存储在 http_proxy 和 https_proxy 这两个列表中并已将 txt 保存在桌面")
end_time = timeit.default_timer()
print("共爬取{}个 http 代理,可用{}个,爬取{}个 https 代理,可用{}个,总耗时{}s".format(Unhttp_proxy_num,
http_proxy_num, Unhttps_proxy_num, https_proxy_num, end_time-start_time))

提示:由于代理的时效性,不建议长期存储,建议 “即用即爬”,另外请勿重复使用,短时间内使用两三次就会封掉 ip,如果 ip 被封的话挂 vpn 即可

效果展示#

image

image

image

可以看到免费的高匿 ip 的有效个数还是很少的,376 个 http 代理只有 53 个可用,409 和 https 代理只有 72 个可用。

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.