代理的基本原理 及用Xpath爬取代理网站IP列表 测试并存入数据库

 前言

        在网络爬虫中,有些网站会设置反爬虫措施,服务器会检测某个IP在单位时间内的请求次数,如果超过了这个阈值,就会直接拒绝服务,返回一些错误信息,例如 403 Forbidden,“您的IP访问频率过高”这样的提示,这就是IP被封禁了,这种情况下就需要进行IP伪装。

代理的基本原理

        代理实际上指的是代理服务器(proxy server),它的功能是代理网络用户去去的网络信息,是网络信息的中转站,一般情况下,请求访问网站时,是先发送请求给Web服务器,Web服务器再把响应传回给我们,如下图:

        如果设置了代理服务器,实际上是在本机和服务器之间建立了一个桥梁,此时本机不是直接向Web服务器发起请求,而是向代理服务器发出请求,请求会发送给代理服务器,然后由代理服务器再发送给Web服务器,接着由代理服务器再把Web服务器返回的响应转发给本机,图解如下,这样同样可以正常访问网页,但这个过程中Web服务器识别出的真实IP就不再是我们本机的IP了,就成功实现了IP伪装,这就是代理的基本原理。 

代理的作用 

  • 突破自身IP访问限制,访问一些平时不能访问的站点
  • 访问一些单位或团体内部资源
  • 提高访问速度:通常代理服务器都设置一个较大的硬盘缓冲区,当有外界信息通过时,同时也将其保存到缓冲区中,当其他用户再访问相同的信息时,则直接由缓冲区中取出信息,传给用户,以提高访问速度
  • 隐藏真实IP,当作防火墙保护自身网络安全,在爬虫中可以防止自身的IP被封锁 

代理分类 

1. 根据协议区分 

  • FTP 代理服务器:主要用于访问FTP服务器,一般有上传、下载以及缓存功能,端口一般为 21、2121等
  • HTTP 代理服务器:主要用于访问网页,一般有内容过滤和缓存功能,端口一般为 80、8080、3128等
  • SSL/TLS 代理:主要用于访问加密网站,一般有 SSL 或 TLS 加密功能(最高支持128位加密强度),端口一般为 443
  • RTSP 代理:主要用于访问Real流媒体服务器,一般有缓存功能,端口一般为 554
  • Telnet 代理:主要用于 telnet 远程控制(黑客入侵计算机时常用于隐藏身份),端口一般为 23
  • POP3/SMTP 代理:主要用于 POP3/SMTP 方式收发右键,一般有缓存功能,端口一般为 110/25
  • SOCKS 代理:只是单纯传递数据包,不关心具体协议和用法,所以速度很快,一般有缓存功能,端口一般为 1080 。SOCKS 代理协议又分为 SOCK4 和 SOCK5,SOCK4 只支持TCP,而后者支持 TCP 和 UDP,还支持各种身份验证机制、服务器端域名解析等。

2. 根据匿名程度区分

  • 高度匿名代理:会将数据包原封不动的转发,在服务端看来就好像真的是一个普通客户端在访问,而记录的IP是代理服务器的IP
  • 普通匿名代理:会在数据包上做一些改动,服务端上有可能发现这是个代理服务器,也有一定几率追查到客户端的真实IP。代理服务器通常会加入的HTTP头有 HTTP_VIA 和  HTTP_X_FORWORD_FOR
  • 透明代理:不但改动了数据包,还会告诉服务器客户端的真实IP。这种代理除了能用缓存技术提高浏览速度,能用内容过滤提高安全性之外,并无其他显著作用,最常见的例子是内网中的硬件防火墙
  • 间谍代理:指组织或个人创建的用于记录用户传输的数据,然后进行研究、监控等目的的地理服务器

常见代理设置

  • 使用网上的免费代理:最好使用高匿代理,这里将在下面爬取代理网站中所有代理
  • 使用付费代理服务:付费代理会比免费代理更为稳定,效果更好,按需求选择
  • ADSL 拨号:拨一次号换一次 IP,稳定性高

爬取代理网站IP

1. 定位所需网页元素节点,获取内容

        以云代理网站为例,网站地址:云代理 - 高品质http代理ip供应平台/每天分享大量免费代理IP

        这里我选择的是用Xpath解析库进行匹配爬取,在使用之前,需要确保安装好了 lxml 库,lxml 是 Python 的一个解析库,支持 HTML 和 XML 解析,支持 Xpath 解析方式,而且解析效率非常高,安装方式为:

pip3 install lxml

        如果没报错,则安装成功,若出现报错,例如缺少 libxml2 库等信息,可以采用 wheel 方式安装,推荐链接:https://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml,下载对应的 wheel 文件,找到本地安装 Python 版本和系统对应的 lxml 版本,例如 Windows 64 位、python 3.6,就选择如下方式下载到本地:

pip3 install lxml-3.8.0-cp36-cp36m-win_amd64.whl

        如图可以看出,我们所需要的信息全都在 <tr> 标签中的 <td> 里,这就能写出 Xpath 表达式:

# 将 html 字符串转换为 _ELEMENT 对象
html = etree.HTML(html)
result = html.xpath("//div[@id='list']//tr/td/text()")

        可以通过浏览器插件 Xpath Helper 来验证是否正确,相关插件的安装及使用方式,可以参考博文:

selenium中如何配置使用浏览器插件 及 xpath-helper、chropath下载方式_Yy_Rose的博客-CSDN博客_xpath 插件

2. 连接 MySQL 数据库,创建表格

import pymysql

# 获取数据库连接
db = pymysql.connect(host='localhost', user='root', passwd='123456', port=3306
                     , db='proxy')
# 获取游标
cursor = db.cursor()
# 若无 proxy 数据库可执行如下操作
# cursor.execute("create database proxy default character set utf8")
# 删除之前创建的表格,不然新的数据会直接添加在旧数据之后,造成数据冗余
cursor.execute("drop table proxy_pool")
create_sql = "create table if not exists proxy_pool(" \
             "代理IP地址 varchar(255) not null," \
             "端口 int not null," \
             "匿名度 text not null," \
             "类型 varchar(255) not null," \
             "支持 varchar(255) not null," \
             "位置 text not null," \
             "响应速度 text not null," \
             "最后验证时间 varchar(255) not null)engine=innodb default charset=utf8;"
cursor.execute(create_sql)

3. 写入本地 txt

proxy_dir = 'proxy_pool.txt'
# 以追加模式写入
with open(proxy_dir, 'a', encoding='utf-8') as f:
    # json.dumps序列化时对中文默认使用的ascii编码,想输出真正的中文需要指定ensure_ascii=False
    f.write(json.dumps(content, ensure_ascii=False) + '\n')

4. 测试获取到的IP是否可用 

         代理IP样式为:代理IP地址 :端口,所以需要先获取相关内容,存入标准代理样式列表:

ip_and_port.append(proxy_infos[0].text.strip() + ':' + proxy_infos[1].text.strip())

        以请求访问百度为例,传入获取到的代理IP,若网站返回状态码为 200 则说明此代理IP有效,将有效的代理IP存入相应的可用代理列表中并输出显示,若出现其他情况则判断该代理IP无效,并显示该代理IP。

def ip_test(ip_and_port_list, usable_ip_list):
    url = "https://www.baidu.com/"
    try:
        for ip in ip_and_port_list:
            try:
                response = requests.get(url, proxy={'https': ip},
                                        headers=headers, timeout=0.5)
                if response.status_code == 200:
                    usable_ip_list.append(ip)
                    print("该代理ip有效:" + ip)
                else:
                    print("该代理ip无效:" + ip)
            except:
                print("该代理ip无效:" + ip)
    except:
        return None

 完整代码

# @Author : Yy_Rose
import requests
from lxml import etree
import json
from requests.exceptions import RequestException
import time
import pymysql

# 获取数据库连接
db = pymysql.connect(host='localhost', user='root', passwd='123456', port=3306
                     , db='proxy')
# 获取游标
cursor = db.cursor()
# 若无 proxy 数据库可执行如下操作
# cursor.execute("create database proxy default character set utf8")
# 删除之前创建的表格,不然新的数据会直接添加在旧数据之后,造成数据冗余
cursor.execute("drop table proxy_pool")
create_sql = "create table if not exists proxy_pool(" \
             "代理IP地址 varchar(255) not null," \
             "端口 int not null," \
             "匿名度 text not null," \
             "类型 varchar(255) not null," \
             "支持 varchar(255) not null," \
             "位置 text not null," \
             "响应速度 text not null," \
             "最后验证时间 varchar(255) not null)engine=innodb default charset=utf8;"
# 执行数据库操作
cursor.execute(create_sql)

# 构建请求头
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64)'
                  ' AppleWebKit/537.36 (KHTML, like Gecko)'
                  ' Chrome/86.0.4240.198 Safari/537.36',
    'Cookie': 'Hm_lvt_c4dd741ab3585e047d56cf99ebbbe102=1640671968,'
              '1641362375;'
              ' Hm_lpvt_c4dd741ab3585e047d56cf99ebbbe102=1641362386'
}


# 获取 HTTPResponse 类型的对象
def get_one_page(url):
    try:
        response = requests.get(url, headers=headers)
        if response.status_code == 200:
            # 编码
            response.encoding = 'gbk'
            return response.text
        return None
    except RequestException:
        return None


# 解析获取内容
def parse_one_page(proxy_pool, html, ip_and_port):
    # 将 html 字符串转换为 _ELEMENT 对象
    content = etree.HTML(html)
    # 第一行为 th 标签内容,故从第二行开始
    tr_cnotent_list = content.xpath("//div[@id='list']//tr")[1:]
    for proxy_infos in tr_cnotent_list:
        # strip() 方法用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列
        proxy_pool.append([proxy_infos[0].text.strip(),
                           proxy_infos[1].text.strip(),
                           proxy_infos[2].text.strip(),
                           proxy_infos[3].text.strip(),
                           proxy_infos[4].text.strip(),
                           proxy_infos[5].text.strip(),
                           proxy_infos[6].text.strip(),
                           proxy_infos[7].text.strip()])
        ip_and_port.append(
            proxy_infos[0].text.strip() + ':' + proxy_infos[1].text.strip())
        ip = proxy_infos[0].text.strip()
        port = proxy_infos[1].text.strip()
        anonymity = proxy_infos[2].text.strip()
        ip_type = proxy_infos[3].text.strip()
        support_type = proxy_infos[4].text.strip()
        position = proxy_infos[5].text.strip()
        response_speed = proxy_infos[6].text.strip()
        verify_time = proxy_infos[7].text.strip()
        # 将获取到的数据插入到数据库中
        insert_into = (
            "INSERT INTO proxy_pool(代理IP地址,端口,匿名度,类型,支持,位置,响应速度,最后验证时间)"
            "VALUES(%s,%s,%s,%s,%s,%s,%s,%s);")
        data_into = (
            ip, port, anonymity, ip_type, support_type, position,
            response_speed,
            verify_time)
        cursor.execute(insert_into, data_into)
        # 将 insert 语句提交给数据库
        db.commit()


# 写入本地 txt
def wirte_to_txt(content):
    proxy_dir = 'proxy_pool.txt'
    # 以追加模式写入
    with open(proxy_dir, 'a', encoding='utf-8') as f:
        # json.dumps序列化时对中文默认使用的ascii编码,想输出真正的中文需要指定ensure_ascii=False
        f.write(json.dumps(content, ensure_ascii=False) + '\n')


# 打印内容到控制台
def print_list(proxy_list):
    for i in range(10):
        print(proxy_list[i])
        wirte_to_txt(proxy_list[i])


# 测试IP是否可用
def ip_test(ip_and_port_list, usable_ip_list):
    url = "https://www.baidu.com/"
    try:
        for ip in ip_and_port_list:
            try:
                response = requests.get(url, proxy={'https': ip},
                                        headers=headers, timeout=0.5)
                if response.status_code == 200:
                    usable_ip_list.append(ip)
                    print("该代理ip有效:" + ip)
                else:
                    print("该代理ip无效:" + ip)
            except:
                print("该代理ip无效:" + ip)
    except:
        return None


def main(offset):
    # 分页爬取
    url = 'http://www.ip3366.net/?stype=1&page=' + str(offset)
    html = get_one_page(url)
    proxy_pool = []
    usable_ip = []
    ip_port = []
    parse_one_page(proxy_pool, html, ip_port)
    print("以下为查询到的数据:")
    print_list(proxy_pool)
    print('=' * 50)
    ip_test(ip_port, usable_ip)
    print('=' * 50)
    print("有效的代理ip为:")
    # 判断列表是否为空
    if usable_ip:
        for usable in usable_ip:
            print(usable, end=' ')
    else:
        print(f"很遗憾,第{offset}页的代理IP全都无效")
    print('=' * 50)


if __name__ == '__main__':
    num = int(input("请输出想查询多少页ip:"))
    # 共10页,100条记录,range()为左闭右开
    for page in range(1, num + 1):
        main(offset=page)
        time.sleep(2)

控制台打印: 

代理IP测试: 

本地 txt:

数据库效果:

写项目过程中的报错

pymysql.err.ProgrammingError: (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'get/post支持 varchar(255) not null,位置 text not null,响应速度 text not ' at line 1")

原因:创建表格的字段名与MySQL关键字冲突,get/post 支持改为支持即可

TypeError: not all arguments converted during string formatting

原因:并非所有参数都在字符串格式化期间转换

insert_into = (
        "INSERT INTO proxy_pool(代理IP地址,端口,匿名度,类型,支持,位置,响应速度,最后验证时间)"
        "VALUES(?,?,?,?,?,?,?,?);")

分析可能性一:

        一开始使用的是 ?占位符, 其可使用参数的位置,标识变量,用于变量传值,支持和最后验证时间列不止一个元素,?无法完全匹配,导致参数数量不对应

        更改为 %s 就好了,%s 表示 str(),字符串可以包含空格、特殊字符,能一一匹配到完整元素

insert_into = (
        "INSERT INTO proxy_pool(代理IP地址,端口,匿名度,类型,支持,位置,响应速度,最后验证时间)"
        "VALUES(%s,%s,%s,%s,%s,%s,%s,%s);")

分析可能性二:

import sqlite3

在SQLite数据库中,可以直接使用第一种方法插入一条或多条数据,经测试成功插入

import pymysql

在MySQL数据库中,不支持第一种插入方法

pymysql.err.InterfaceError: (0, '')

原因:数据库连接在创建表格后就关闭了,导致无法进行数据库操作,插入数据操作时报错

pymysql.err.DataError: (1366, "Incorrect string value: '\\xE9\\xAB\\x98\\xE5\\x8C\\xBF...' for column '匿名度' at row 1")

原因:创建数据库时未将字符集设置为 utf-8 格式,更改为 utf8 -- UTF-8 Unicode 即可,以下为之前导致报错的样式:

总结 

        免费的代理 IP 大部分都是无效的,不稳定的,以上为代理的相关知识及如何爬取代理网站代理 IP 列表的相关介绍,欢迎评论指正交流~

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值