步骤/目录:
1.需求分析
2.mysql建表
3.网页请求分析
4.爬虫编写
    (0)安装所需模块
    (1)mysql存取
    (2)爬虫主体编写
    (3)其他爬虫补充
    (4)实际运行
    (5)检测爬取的ip
5.总结与改进

本文首发于个人博客https://lisper517.top/index.php/archives/44/,转载请注明出处。
本文的目的是构建自己的ip池。
本文写作日期为2022年9月4日。操作的平台为win10,编辑器为VS code。

爬虫写的好,牢房进的早。使用爬虫时不要占用服务器太多流量,否则可能惹祸上身。

除了python与爬虫,这里实际上还需要了解html格式与mysql命令。可以在 runoob-htmlrunoob-mysql 找到初步的学习资料,或者查看笔者其他的文章。

最后,笔者的能力有限,也并非CS从业人员,很多地方难免有纰漏或者不符合代码原则的地方,请在评论中指出。

1.需求分析

之前说过,在对别人的网页服务器进行爬取时要注意不能占用太多服务器资源。但有时候,我们确实需要在短时间内对某个网页高频访问,又担心自己被封ip,那么此时维护一个ip池就非常有必要,我们需要对网络上的ip(每个ip背后是一个或一群服务器)进行测试,看是否能利用这些ip作为代理服务器访问其他网页。本文先介绍如何爬取ip,后续文章会介绍如何用自己的ip池爬取网页,以及代理失效时更换代理。在爬虫的一些较高级的应用中,一个ipool是非常基础且不可或缺的,所以建议学会scrapy后就应该尽快建立自己的ipool(除非没有高频访问某个网页的需求)。

ip池的构建和使用分为两步,第一步是筛选ip。世界上的公网ipv4地址最多有 4,294,967,296 个(256^4。实际上有一些被局域网占用,有一些不会使用),要一个个去验证这些ip耗时太长,何况还需要对已存在的ip进行验证、剔除失效ip。所以,这里使用一些提供免费代理ip的网页缩小ip搜索范围,爬取到的代理ip不做验证,都放入库中(这里使用mysql)。
第二步是对mysql中已有的ip进行验证,挑选出对某个网址有效的代理ip。这一步严格来讲和第一步关系不大,它可以在前一个步骤进行时也同步进行,更重要的是这一步是在其他爬虫开始后才进行,下面就讲解一下免费代理ip的使用。

免费代理可分为HTTP或HTTPS,高匿名和低匿名。如果爬虫运行时经过了这些免费代理,毕竟还是别人的服务器,你的请求和响应是到了别人那里,所以用代理服务器时不要传递一些私密信息。实在有必要的话,自己买服务器做转发。最后,同一个代理ip,可能你用它代理一个网站可以,代理另一个网站就不行,这也是不确定因素,所以爬到的代理ip在使用时还要对特定的网址进行测试,其实也是比较麻烦的,如果有紧急需求,建议买一些付费的代理ip。

2.mysql建表

在mysql中存储收集到的ip,需要新建以下列:
ip,字符串形式,VARCHAR(50)(为了将来兼容ipv6,稍微长一点);
端口号,数值形式,SMALLINT UNSIGNED(刚好是0~65535);
匿名度,2个bit位,BIT(2)(0表示低匿名,1表示高匿名,3表示未知);
类型(HTTP还是HTTPS),2个bit位,BIT(2)(0表示HTTP,1表示HTTPS,2表示都可,3表示其他);
ip物理位置,字符串,VARCHAR(100);
响应速度,MEDIUMINT,单位是毫秒ms;
最后一次验证的时间,日期时间格式。

建表的语句在程序中将给出。其实对这种不断更新、添加的数据,redis更加合适,有关redis的内容将在后续的文章中介绍。

3.网页请求分析

这里使用的提供代理ip的网站为(笔者给出的例子中,只使用了第一个网址):

http://www.ip3366.net/free/
https://www.kuaidaili.com
https://ip.jiangxianli.com/
https://www.cool-proxy.net/
https://free-proxy-list.net
https://www.ipaddress.com/proxy-list/
http://proxy-list.org/english/
https://www.proxynova.com/proxy-server-list/
http://pubproxy.com/api/proxy?limit=5&format=txt&type=http&level=anonymous&last_check=60&no_country=CN
https://www.rmccurdy.com/.scripts/proxy/good.txt
http://spys.one/en/anonymous-proxy-list/
https://raw.githubusercontent.com/sunny9577/proxy-scraper/master/proxies.json
https://raw.githubusercontent.com/a2u/free-proxy-list/master/free-proxy-list.txt
https://raw.githubusercontent.com/clarketm/proxy-list/master/proxy-list.txt
https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/http.txt

这些网页的请求响应都挺简单的,这里就不专门介绍了。由于涉及多个网站,这里将使用多线程的方法,持续对各个ip网站爬取。唯一需要注意的是对每个网站,爬取ip的速度不要太快了,虽然对单个网站爬取速度不快,但毕竟有这么多ip网站,实际上ip入库的速度挺快的。

4.爬虫编写

(0)安装所需模块

需要用到requests(处理网络请求)、bs4(对响应内容进行解析)、pymysql(连接mysql数据库)、lxml(网页解析器)、cryptography(连接mysql时密码加密)库,在cmd中使用 pip install requests bs4 pymysql lxml cryptography 下载(下载失败就一直使用上述命令,直到下载成功)。之后新建一个文件夹,用VS Code打开该文件夹、新建一个py文件,进行下面的操作。

(1)mysql存取

使用mysql存储ip,其他爬虫需要通过这个类存取ip。自己使用时,记得把mysql连接的地址、端口、密码更改一下。

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import pymysql, time, os, logging
from traceback import print_exc

if not os.path.isdir(os.path.join(os.getcwd(), 'logs')):
    os.mkdir(os.path.join(os.getcwd(), 'logs'))


class MysqlOperationFailed(Exception):
    """若更改mysql出现错误则抛出这个异常。"""

    def __init__(self):
        message = "Failed to change data in MySQL, please check. "
        super().__init__(message)


class mysql_api():
    """通过这个界面实现提交爬取的ip、取出ip的操作。"""
    conn = pymysql.connect(host='127.0.0.1',
                           port=53306,
                           user='root',
                           passwd='mysqlpasswd',
                           db='mysql',
                           charset='utf8')
    cur = conn.cursor()

    def __init__(self, log_name: str):
        """初始函数,需要提交一个log_name作为进程的log文件名。"""
        self.cur = mysql_api.conn.cursor()
        mysql_api.check_table()
        self.logging = logging.basicConfig(
            level=logging.INFO,
            filename=os.path.join(
                os.getcwd(), 'logs',
                '{}-log{}.txt'.format(log_name,
                                      time.strftime("%Y-%m-%d-%H_%M_%S"))))

    def __del__(self):
        self.cur.close()

    @staticmethod
    def check_table():
        """检查一下mysql中是否已经建好了表,若表不存在则新建表。这里并没有检查:若表存在,表的格式是否正确。"""
        cur, conn = mysql_api.cur, mysql_api.conn
        command = '''
        CREATE TABLE IF NOT EXISTS ipool ( 
        no BIGINT UNSIGNED AUTO_INCREMENT, 
        ip VARCHAR(50) NOT NULL DEFAULT "0.0.0.0" COMMENT "IP address", 
        port SMALLINT UNSIGNED NOT NULL DEFAULT 0 COMMENT "port", 
        anonymous BIT(2) NOT NULL DEFAULT 3 COMMENT "whether ip is anonymous", 
        type BIT(2) NOT NULL DEFAULT 3 COMMENT "HTTP or HTTPS or both or others", 
        physical_address VARCHAR(100) NOT NULL DEFAULT "unknown" COMMENT "where is the server", 
        response_time MEDIUMINT NOT NULL DEFAULT -1 COMMENT "response_time in microseconds", 
        last_verify DATETIME NOT NULL DEFAULT '1000-01-01 00:00:00' COMMENT "last verify time", 
        created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 
        PRIMARY KEY (no));'''
        command = command.replace('\n', '')
        command = command.replace('    ', '')
        try:
            cur.execute("CREATE DATABASE IF NOT EXISTS ipool;")
            cur.execute("USE ipool;")
            cur.execute(command)
            conn.commit()
        except:
            conn.rollback()
            raise MysqlOperationFailed

    def save_ips(self, ips_list: list):
        """存入多个ip(以字典的列表形式提交)。注意这里只是对不存在的数据添加默认值,没有验证数据类型等。
        另外注意,响应时间单位为ms。"""
        cur, conn = self.cur, mysql_api.conn
        command = '''INSERT INTO ipool (ip,port,anonymous,type,physical_address,response_time,last_verify) VALUES '''
        for ip_dict in ips_list:
            ip, port, anonymous, type_, physical_address, response_time, last_verify = "0.0.0.0", 0, 3, 3, 'unknown', -1, "1000-01-01 00:00:00"
            if 'ip' in ip_dict.keys():
                ip = ip_dict['ip']
            if 'port' in ip_dict.keys():
                port = ip_dict['port']
            if 'anonymous' in ip_dict.keys():
                anonymous = ip_dict['anonymous']
            if 'type' in ip_dict.keys():
                type_ = ip_dict['type']
            if 'physical_address' in ip_dict.keys():
                physical_address = ip_dict['physical_address']
            if 'response_time' in ip_dict.keys():
                response_time = ip_dict['response_time']
            if 'last_verify' in ip_dict.keys():
                last_verify = ip_dict['last_verify']
            command += '("{}",{},{},{},"{}",{},"{}"),'.format(
                ip, port, anonymous, type_, physical_address, response_time,
                last_verify)
        command = command[:-1] + ';'
        try:
            cur.execute(command)
            conn.commit()
        except:
            conn.rollback()
            raise MysqlOperationFailed

    @staticmethod
    def check_repeat():
        """检查爬到的数据里有没有重复的,只检查ip和port都相同的。如果有重复的,保留最新的一条记录,也就是no最大的。"""
        cur, conn = mysql_api.cur, mysql_api.conn
        cur.execute('USE ipool;')
        cur.execute(
            'SELECT ip,port FROM ipool GROUP BY ip,port HAVING COUNT(*)>1;')
        repeated_items = cur.fetchall()
        if len(repeated_items) == 0:
            print('\n数据库中无重复数据。')
            return
        else:
            print('\n数据库中检测到重复数据,有{}种。'.format(len(repeated_items)))
            command = '''
            DELETE FROM ipool WHERE no IN (SELECT no FROM
            (SELECT no FROM ipool WHERE 
            (ip,port) IN (SELECT ip,port FROM ipool GROUP BY ip,port HAVING COUNT(*)>1) 
            AND no NOT IN (SELECT MAX(no) FROM ipool GROUP BY ip,port HAVING COUNT(*)>1)) AS a);'''
            command = command.replace('\n', '')
            command = command.replace('    ', '')
            try:
                cur.execute(command)
                conn.commit()
            except:
                conn.rollback()
                raise MysqlOperationFailed

    @staticmethod
    def get_ips(anonymous: list = [1],
                type_: list = [2],
                response_time: int = 30000,
                last_verify_interval: str = '48:00:00',
                limit: int = 1000) -> tuple:
        """根据要求返回多个ip,只返回ip和port。
        
        :param last_verify_interval 用于指定上次验证的时间距现在不超过多久。"""
        cur, conn = mysql_api.cur, mysql_api.conn
        cur.execute('USE ipool;')
        anonymous, type_ = tuple(anonymous), tuple(type_)
        command = '''
        SELECT ip,port FROM ipool 
        WHERE anonymous IN {} AND type IN {} 
        AND response_time BETWEEN 0 AND {} AND (NOW()-last_verify)<"{}" 
        ORDER BY response_time LIMIT {};
        '''.format(anonymous, type_, response_time, last_verify_interval,
                   limit)
        command = command.replace('\n', '')
        command = command.replace('    ', '')
        print(command)
        cur.execute(command)
        return cur.fetchall()


if __name__ == "__main__":
    try:
        a = mysql_api('kuaidaili')
        ip_list = []
        for i in range(1, 256):
            ip_dict = {
                'ip': '192.168.1.{}'.format(i),
                'port': i,
                'response_time': i * 10,
                'last_verify': '2022-09-05 12:00:00'
            }
            ip_list.append(ip_dict)
        a.save_ips(ip_list)
        a.check_repeat()
        print(a.get_ips([0, 1, 2, 3], [0, 1, 2, 3], 30000))
    except:
        print_exc()

(2)爬虫主体编写

这里稍微把前面mysql存取的部分进行了更改,一并写出。单个ip网站的爬虫使用了递归调用的方法,不太适合页码比较多的情况。
这里只是写了对 http://www.ip3366.net/free/ 的爬虫、并用线程的方法开启它,其他爬虫可在后面自行补充。
最后,自己使用时,记得把mysql连接的地址、端口、密码更改一下。

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import pymysql, os, time, logging
from traceback import print_exc

if not os.path.isdir(os.path.join(os.getcwd(), 'logs')):
    os.mkdir(os.path.join(os.getcwd(), 'logs'))
logging.basicConfig(level=logging.INFO,
                    filename=os.path.join(
                        os.getcwd(), 'logs', 'log-{}.txt'.format(
                            time.strftime("%Y-%m-%d-%H_%M_%S"))))


class MysqlOperationFailed(Exception):
    """若更改mysql出现错误则抛出这个异常。"""

    def __init__(self):
        message = "Failed to change data in MySQL, please check. "
        super().__init__(message)


class mysql_api():
    """通过这个界面实现提交爬取的ip、取出ip的操作。"""
    conn = pymysql.connect(host='127.0.0.1',
                           port=53306,
                           user='root',
                           passwd='mysqlpasswd',
                           db='mysql',
                           charset='utf8')
    cur = conn.cursor()

    def __init__(self):
        self.cur = mysql_api.conn.cursor()
        mysql_api.check_table()
        message = 'one of mysql_api object started'
        logging.info(message)

    def __del__(self):
        self.cur.close()
        message = 'one of mysql_api object stopped'
        logging.info(message)

    @staticmethod
    def check_table():
        """检查一下mysql中是否已经建好了表,若表不存在则新建表。这里并没有检查:若表存在,表的格式是否正确。"""
        cur, conn = mysql_api.cur, mysql_api.conn
        command = '''
        CREATE TABLE IF NOT EXISTS ipool ( 
        no BIGINT UNSIGNED AUTO_INCREMENT, 
        ip VARCHAR(50) NOT NULL DEFAULT "0.0.0.0" COMMENT "IP address", 
        port SMALLINT UNSIGNED NOT NULL DEFAULT 0 COMMENT "port", 
        anonymous BIT(2) NOT NULL DEFAULT 3 COMMENT "whether ip is anonymous", 
        type BIT(2) NOT NULL DEFAULT 3 COMMENT "HTTP or HTTPS or both or others", 
        physical_address VARCHAR(100) NOT NULL DEFAULT "unknown" COMMENT "where is the server", 
        response_time MEDIUMINT NOT NULL DEFAULT -1 COMMENT "response_time in microseconds", 
        last_verify DATETIME NOT NULL DEFAULT '1000-01-01 00:00:00' COMMENT "last verify time", 
        created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 
        PRIMARY KEY (no));'''
        command = command.replace('\n', '')
        command = command.replace('    ', '')
        try:
            cur.execute("CREATE DATABASE IF NOT EXISTS ipool;")
            cur.execute("USE ipool;")
            cur.execute(command)
            conn.commit()
            message = 'database ipool and table ipool checked'
            logging.info(message)
        except:
            conn.rollback()
            raise MysqlOperationFailed

    def save_ips(self, ips_list: list):
        """存入多个ip(以字典的列表形式提交)。注意这里只是对不存在的数据添加默认值,没有验证数据类型等。
        另外注意,响应时间单位为ms。"""
        cur, conn = self.cur, mysql_api.conn
        command = '''INSERT INTO ipool (ip,port,anonymous,type,physical_address,response_time,last_verify) VALUES '''
        for ip_dict in ips_list:
            ip, port, anonymous, type_, physical_address, response_time, last_verify = "0.0.0.0", 0, 3, 3, 'unknown', -1, "1000-01-01 00:00:00"
            if 'ip' in ip_dict.keys():
                ip = ip_dict['ip']
            if 'port' in ip_dict.keys():
                port = ip_dict['port']
            if 'anonymous' in ip_dict.keys():
                anonymous = ip_dict['anonymous']
            if 'type' in ip_dict.keys():
                type_ = ip_dict['type']
            if 'physical_address' in ip_dict.keys():
                physical_address = ip_dict['physical_address']
            if 'response_time' in ip_dict.keys():
                response_time = ip_dict['response_time']
            if 'last_verify' in ip_dict.keys():
                last_verify = ip_dict['last_verify']
            command += '("{}",{},{},{},"{}",{},"{}"),'.format(
                ip, port, anonymous, type_, physical_address, response_time,
                last_verify)
            message = 'trying to save ip:port={}:{}'.format(ip, port)
            logging.info(message)
        command = command[:-1] + ';'
        try:
            cur.execute(command)
            conn.commit()
            message = 'saving ips successfully'
            logging.info(message)
        except:
            conn.rollback()
            raise MysqlOperationFailed

    def check_repeat(self):
        """检查爬到的数据里有没有重复的,只检查ip和port都相同的。如果有重复的,保留最新的一条记录,也就是no最大的。"""
        cur, conn = mysql_api.cur, mysql_api.conn
        cur.execute('USE ipool;')
        cur.execute(
            'SELECT ip,port FROM ipool GROUP BY ip,port HAVING COUNT(*)>1;')
        repeated_items = cur.fetchall()
        if len(repeated_items) == 0:
            message = 'no repeated ip:port in ipool'
            logging.info(message)
            return
        else:
            message = 'found ip:port repeated {} kinds'.format(
                len(repeated_items))
            logging.info(message)
            command = '''
            DELETE FROM ipool WHERE no IN (SELECT no FROM
            (SELECT no FROM ipool WHERE 
            (ip,port) IN (SELECT ip,port FROM ipool GROUP BY ip,port HAVING COUNT(*)>1) 
            AND no NOT IN (SELECT MAX(no) FROM ipool GROUP BY ip,port HAVING COUNT(*)>1)) AS a);'''
            command = command.replace('\n', '')
            command = command.replace('    ', '')
            try:
                cur.execute(command)
                conn.commit()
                message = 'repeated ip:port deleted successfully. '
                logging.info(message)
            except:
                conn.rollback()
                raise MysqlOperationFailed

    @staticmethod
    def get_ips(anonymous: list = [1],
                type_: list = [2],
                response_time: int = 30000,
                last_verify_interval: str = '48:00:00',
                limit: int = 1000) -> tuple:
        """根据要求返回多个ip,只返回ip和port。
        
        :param last_verify_interval 用于指定上次验证的时间距现在不超过多久。"""
        cur, conn = mysql_api.cur, mysql_api.conn
        cur.execute('USE ipool;')
        anonymous, type_ = tuple(anonymous), tuple(type_)
        command = '''
        SELECT ip,port FROM ipool 
        WHERE anonymous IN {} AND type IN {} 
        AND response_time BETWEEN 0 AND {} AND (NOW()-last_verify)<"{}" 
        ORDER BY response_time LIMIT {};
        '''.format(anonymous, type_, response_time, last_verify_interval,
                   limit)
        command = command.replace('\n', '')
        command = command.replace('    ', '')
        print(command)
        cur.execute(command)
        ips = cur.fetchall()
        return ips


import threading, requests, re
from lxml import etree

headers = {
    'User-Agent':
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'
}


class spider_threads():
    """爬虫类,通过多线程的方法同时爬取多个网站。"""
    websites = [
        'http://www.ip3366.net/free/'
    ]
    url_spidered_list = []
    for i in range(len(websites)):
        url_spidered_list.append(set())

    def __init__(self):
        self.threads = len(spider_threads.websites)

    @staticmethod
    def get_session():
        """返回一个session对象。"""
        session = requests.Session()
        session.mount('http://', requests.adapters.HTTPAdapter(max_retries=3))
        session.mount('https://', requests.adapters.HTTPAdapter(max_retries=3))
        return session

    class spider_website():
        """定义一个网站爬虫的基本操作。"""

        def __init__(self, no: int):
            self.session = spider_threads.get_session()
            self.base_url = spider_threads.websites[no]
            self.url_spidered = spider_threads.url_spidered_list[no]
            self.kind = re.search(r'\.{1}.+\.{1}',
                                  self.base_url).group().replace('.', '')
            self.mysql_api = mysql_api()

        def get_html(self, url):
            """对一个页面,返回其响应对象。"""
            global headers
            session = self.session
            try:
                html = session.get(url, timeout=30, headers=headers)
            except requests.exceptions.TooManyRedirects as e:
                message = "网页重定向,休息10min({})。".format(self.kind)
                logging.warning(message)
                print('\r' + message, end='')
                time.sleep(600)
                html = session.get(url, timeout=30, headers=headers)
            html.encoding = html.apparent_encoding
            return html

        def get_ip_recursively(self, url):
            """这个方法用于递归调用爬虫,将在子类中重写。"""
            pass

        def start_spider(self):
            """这个方法用于开启爬虫,将在子类中重写。"""
            pass

    class spider_ip3366(spider_website):
        """ip3366的爬虫类。"""

        def __init__(self):
            super().__init__(0)
            self.start_urls = [
                self.base_url, 'http://www.ip3366.net/free/?stype=2'
            ]

        def get_ip_recursively(self, url):
            """这是一个递归调用的爬虫。它将爬取页面上的信息,将爬取过的网页存入字典,对页面上新的网页进行爬取。
            
            重写了父类方法。"""
            html = self.get_html(url)
            time.sleep(15)
            message = '1 page crawled:{}'.format(url)
            logging.info(message)
            html = etree.HTML(html.content)
            ip_nodes = html.xpath(r'//*[@id="list"]/table/tbody/tr')
            ips_list = []
            for ip_node in ip_nodes:
                ip_dict = {}
                ip = ip_node.xpath(r'./td[1]/text()')[0].strip()
                port = int(ip_node.xpath(r'./td[2]/text()')[0].strip())
                anonymous = (0, 1)['高匿' in ip_node.xpath(r'./td[3]/text()')[0]]
                type_ = (0, 1)['HTTPS' in ip_node.xpath(r'./td[4]/text()')[0]]
                physical_address = ip_node.xpath(r'./td[5]/text()')[0].strip()
                response_time = 1000 * int(
                    ip_node.xpath(r'./td[6]/text()')[0].strip().replace(
                        '秒', ''))
                response_time = (response_time, 250)[response_time == 0]
                last_verify = ip_node.xpath(
                    r'./td[7]/text()')[0].strip().replace('/', '-')

                ip_dict['ip'] = ip
                ip_dict['port'] = port
                ip_dict['anonymous'] = anonymous
                ip_dict['type'] = type_
                ip_dict['physical_address'] = physical_address
                ip_dict['response_time'] = response_time
                ip_dict['last_verify'] = last_verify
                ips_list.append(ip_dict)
            self.mysql_api.save_ips(ips_list)

            self.url_spidered.add(url)
            page_urls = html.xpath(r'//*[@id="listnav"]/ul/a/@href')
            for i in range(len(page_urls)):
                page_url = self.base_url + page_urls[i]
                if page_url not in self.url_spidered:
                    self.get_ip_recursively(page_url)
                else:
                    return

        def start_spider(self):
            """这个方法用于开启爬虫,对父类的同名方法进行重写。"""
            for start_url in self.start_urls:
                self.get_ip_recursively(start_url)
            self.mysql_api.check_repeat()

def start_all_spider_process():
    process_list = []
    process_list.append(crawl_website_ip3366().start_spider)
    for i in process_list:
        process_pool.apply_async(i)
        time.sleep(35)
    process_pool.close()
    process_pool.join()


if __name__ == "__main__":
    try:
        start_all_spider_process()
    except:
        traceback.print_exc()

spider_ip3366类是一个范例,其他爬虫也照着这样写就行了,所以剩下的只是简单的重复劳动,这里笔者就不进行展示了。
如果要继续写的话,注意其他网站的爬虫都和spider_ip3366类处于同一个层级,也在spider_threads内部。添加其他网站的爬虫后,在start_all_spider_threads里添加线程即可。

(4)实际运行

自行添加其他网站的爬虫后,运行。

(5)检测爬取的ip

这是相对独立的一个步骤,并且要在使用时同步进行,将在后续的文章中讲解。

5.总结与改进

(1)这个爬虫使用了递归,在内存小一点的机器上其实不太好运行。有可能的话,可以改用递归外的方法(比如用scrapy),专门买一个更加小巧的机器或云服务器,全天运行这个爬虫和ip检验,以便能及时更新自己的ipool。
(2)在上一个例子( Python,爬虫与深度学习(2)——爬虫详解(一)爬取金融机构成交持仓排名 )和本例中,都用到了一些重复的方法,比如连接mysql时添加conn、cur,以及log操作。这部分其实可以在自建库里写方法调用,也免得每次都要更改。
(3)这里所有爬虫使用的log文件都是同一个,不太容易看清楚。后续可以让每个爬虫使用不同的log文件。
(4)除了自己写这种ipool爬虫,其实在github上有其他人做好的成品。笔者在这里推荐两个项目: https://github.com/SpiderClub/haipproxyhttps://github.com/imWildCat/scylla ,这两个项目都可以用docker安装。

更新:docker-scylla可能自带挖矿病毒,详见这个问题: https://github.com/imWildCat/scylla/issues/102

标签: python, 爬虫, mysql

添加新评论