高性能爬虫实现 --- 使用多线程/线程池/多进程/异步协程(包含多个不同爬虫示例进行学习)


前言

对于正常单线程爬虫,速度很慢。通过本节的学习,我们会掌握如何实现更高效的爬虫,主要有多线程,线程池,多进程,异步协程等方法


声明
本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!
本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请联系作者立即删除!

一. 单线程爬虫实现

回顾一个完成的爬虫任务,都是单线程爬虫,我们来看一下单线程完成的代码耗时

import requests
import pymongo


class Aqiyi(object):
    def __init__(self):
        self.client = pymongo.MongoClient(host='127.0.0.1', port=27017)
        self.collection = self.client['spider']['aqy']
        self.headers = {
            'referer': 'https://list.iqiyi.com/www/2/15-------------11-1-1-iqiyi--.html?s_source=PCW_SC',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36',
            'x-requested-with': 'XMLHttpRequest'
        }
        self.url = 'https://pcw-api.iqiyi.com/search/recommend/list'


    def get_data(self, params):
        response = requests.get(self.url, headers=self.headers, params=params)
        return response.json()

    def parse_data(self, data):
        categoryVideos = data['data']['list']
        for video in categoryVideos:
            item = {}
            item['title'] = video['title']
            item['playUrl'] = video['playUrl']
            item['description'] = video['description']
            print(item)
            self.save_data(item)

    def save_data(self, item):
        self.collection.insert_one(item)


    def main(self):
        for page in range(1, 2):
            params = {
                'channel_id': '2',
                'data_type': '1',
                'mode': '11',
                'page_id': page,
                'ret_num': '48',
                'session': 'fc7d98794f15b224b169d328bf8f9f13',
                'three_category_id': '15;must',
            }
            data = self.get_data(params)
            self.parse_data(data)



if __name__ == '__main__':
    t1 = time.time()
    yk = Aqiyi()
    yk.main()
    print("total cost:", time.time() - t1)

二. 多线程爬虫实现

  • 在前面爬虫基础知识案例中我们发现请求回来的总数据不是太多,时间性对来说还是比较快的,那么如果该网站有大量数据等待爬虫爬取,我们是不是需要使用多线程并发来操作爬虫的网络请求呢?

1. 了解多线程的方法使用

  • 在python3中,主线程主进程结束,子线程,子进程不会结束
    为了能够让主线程回收子线程,可以把子线程设置为守护线程,即该线程不重要,主线程结束,子线程结束
t1 = threading.Thread(targe=func,args=(,))
t1.setDaemon(True)
t1.start() #此时线程才会启动

2. 了解队列模块的使用

from queue import Queue
q = Queue(maxsize=100)
item = {}
q.put_nowait(item) #不等待直接放,队列满的时候会报错
q.put(item) #放入数据,队列满的时候回等待
q.get_nowait() #不等待直接取,队列空的时候会报错
q.get() #取出数据,队列为空的时候会等待
q.qsize() #获取队列中现存数据的个数 
q.join() #队列中维持了一个计数,计数不为0时候让主线程阻塞等待,队列计数为0的时候才会继续往后执行
q.task_done() 
# put的时候计数+1,get不会-1,get需要和task_done 一起使用才会-1

3. 多线程思路解析

    1. 把爬虫中的每个步骤封装成函数,分别用线程去执行
    1. 不同的函数通过队列相互通信,函数间解耦

在这里插入图片描述

4. 具体代码实现

对某奇艺进行数据采集

import requests
import pymongo
import time
from queue import Queue
import threading


class Aiqiyi(object):
    def __init__(self):
        self.client = pymongo.MongoClient(host='127.0.0.1', port=27017)
        self.collection = self.client['spider']['aqydxc']
        self.headers = {
            'referer': 'https://list.iqiyi.com/www/2/15-------------11-1-1-iqiyi--.html?s_source=PCW_SC',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36',
            'x-requested-with': 'XMLHttpRequest'
        }
        self.url = 'https://pcw-api.iqiyi.com/search/recommend/list?channel_id=2&data_type=1&mode=11&page_id={}&ret_num=48&session=85dd981b69cead4b60f6d980438a5664&three_category_id=15;must'
        # 用来存储网址的队列
        self.url_queue = Queue()
        # 存放响应数据的队列
        self.json_queue = Queue()
        # 保存的数据队列
        self.content_queue = Queue()

    # 获取到url数据模块
    def get_url(self):
        for i in range(1, 11):
            self.url_queue.put(self.url.format(i))

    # 请求数据模块
    def get_data(self):
        while True:
            url = self.url_queue.get()
            self.url_queue.task_done()
            response = requests.get(url, headers=self.headers)
            print(response.json())
            self.json_queue.put(response.json())

    # 获取提取数据模块
    def parse_data(self):
        while True:
            data = self.json_queue.get()
            self.json_queue.task_done()
            categoryVideos = data['data']['list']
            for video in categoryVideos:
                item = {}
                item['title'] = video['title']
                item['playUrl'] = video['playUrl']
                item['description'] = video['description']
                self.content_queue.put(item)

    # 保存数据模块
    def save_data(self):
        while True:
            item = self.content_queue.get()
            self.collection.insert_one(item)
            self.content_queue.task_done()

    # 主函数和保存数据模块
    def main(self):
        thread_list = []
        t_url = threading.Thread(target=self.get_url)
        thread_list.append(t_url)

        for i in range(3):
            t_parse = threading.Thread(target=self.get_data)
            thread_list.append(t_parse)
        t_content = threading.Thread(target=self.parse_data)
        thread_list.append(t_content)
        t_save = threading.Thread(target=self.save_data)
        thread_list.append(t_save)
        for t in thread_list:
            t.setDaemon(True)  # 把子线程设置为守护线程,当前这个线程不重要,主线程结束,子线程结束
            t.start()

        for q in [self.url_queue, self.json_queue, self.content_queue]:
            q.join()  # 让主线程阻塞,等待队列的计数为0,

        print("主线程结束")

if __name__ == '__main__':
    t1 = time.time()
    con = Aiqiyi()
    con.main()
    print('花费时间为:', time.time()-t1)
  • 注意
    • put会让队列的计数+1,但是单纯的使用get不会让其-1,需要和task_done同时使用才能够-1
    • task_done不能放在另一个队列的put之前,否则可能会出现数据没有处理完成,程序结束的情况

三. 线程池爬虫实现

1. 线程池使用方法介绍

  • 1.线程池,是一种线程的使用模式,它为了降低线程使用中频繁的创建和销毁所带来的资源消耗与代价。通过创建一定数量的线程,让他们时刻准备就绪等待新任务的到达,而任务执行结束之后再重新回来继续待命
  • 2.实例化线程池对象
from concurrent.futures import ThreadPoolExecutor 
def crawl(url): 
	print(url) 
if __name__ == '__main__0': 
	base_url = 'https://jobs.51job.com/pachongkaifa/p{}/' 
	with ThreadPoolExecutor(10) as f: 
	for i in range(1,15): 
		f.submit(crawl,url=base_url.format(i))
  • 3.使用线程池来执行线程任务的步骤如下:
      1. 调用 ThreadPoolExecutor 类的构造器创建一个线程池。
      1. 定义一个普通函数作为线程任务。
      1. 调用 ThreadPoolExecutor 对象的 submit() 方法来提交线程任务。
      1. 当不想提交任何任务时,调用 ThreadPoolExecutor 对象的 shutdown() 方法来关闭线程池。
from concurrent.futures import ThreadPoolExecutor
import threading
import time

# 定义一个准备作为线程任务的函数
def action(max):
    my_sum = 0
    for i in range(max):
        print(threading.current_thread().name + '  ' + str(i))
        my_sum += i
    return my_sum
# 创建一个包含2条线程的线程池
pool = ThreadPoolExecutor(max_workers=2)
# 向线程池提交一个task, 50会作为action()函数的参数
future1 = pool.submit(action, 50)
# 向线程池再提交一个task, 100会作为action()函数的参数
future2 = pool.submit(action, 100)
# 判断future1代表的任务是否结束
print(future1.done())
time.sleep(3)
# 判断future2代表的任务是否结束
print(future2.done())
# 查看future1代表的任务返回的结果
print(future1.result())
# 查看future2代表的任务返回的结果
print(future2.result())
# 关闭线程池 
pool.shutdown()

2. 具体代码实现

对某某招聘进行数据采集

import time

import requests
import pymysql
from concurrent.futures import ThreadPoolExecutor

class Baidu(object):
    def __init__(self):
        self.db = pymysql.connect(host="localhost", user="root", password="root", db="spiders")
        self.cursor = self.db.cursor()
        self.url = 'https://talent.baidu.com/httservice/getPostListNew'
        self.headers = {
            'Referer': 'https://talent.baidu.com/jobs/social-list?search=python',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'
        }

    def get_data(self, page):  # 获取地址和User-Agent
        data = {
            'recruitType': 'SOCIAL',
            'pageSize': 10,
            'keyWord': '',
            'curPage': page,
            'projectType': '',
        }
        response = requests.post(url=self.url, headers=self.headers, data=data)
        return response.json()

    def parse_data(self, response):
        # print(response)
        data_list = response["data"]['list']
        for node in data_list:
            education = node['education'] if node['education'] else '空'

            name = node['name']
            serviceCondition = node['serviceCondition']
            self.save_data(education, name, serviceCondition)

    def create_table(self):
        # 使用预处理语句创建表
        sql = '''
                    CREATE TABLE IF NOT EXISTS baidu(
                        id int primary key auto_increment not null,
                        education VARCHAR(255) NOT NULL, 
                        name VARCHAR(255) NOT NULL, 
                        serviceCondition TEXT)
                    '''
        try:
            self.cursor.execute(sql)
            print("CREATE TABLE SUCCESS.")
        except Exception as ex:
            print(f"CREATE TABLE FAILED,CASE:{ex}")


    def save_data(self,education, name, serviceCondition):
        # SQL 插入语句
        sql = 'INSERT INTO baidu(id, education, name, serviceCondition) values(%s, %s, %s, %s)'
        # 执行 SQL 语句
        try:
            self.cursor.execute(sql, (0, education, name, serviceCondition))
            # 提交到数据库执行
            self.db.commit()
            print('数据插入成功...')
        except Exception as e:
            print(f'数据插入失败: {e}')
            # 如果发生错误就回滚
            self.db.rollback()

    def run(self):
        self.create_table()
        with ThreadPoolExecutor(max_workers=5)as pool:
            for i in range(1, 6):
                response = pool.submit(self.get_data, i)
                self.parse_data(response.result())

        # 关闭数据库连接
        self.db.close()

if __name__ == '__main__':
    t1 = time.time()
    baidu = Baidu()
    baidu.run()
    print("总耗时:", time.time() - t1)

四. 多进程爬虫实现

  • 前面这种方式由于GIL全局锁的存在,多线程在python3下可能只是个摆设,对应的解释器执行其中的内容的时候仅仅是顺序执行,此时我们可以考虑多进程的方式实现,思路和多线程相似,只是对应的api不相同。

1. 了结多进程的方法使用

from multiprocessing import Process  #导入模块
t1 = Process(targe=func,args=(,)) #使用一个进程来执行一个函数
t1.daemon = True  #设置为守护进程
t1.start() #此时进程才会启动

2. 多进程中的队列的使用

  • 多进程中使用普通的队列模块会发生阻塞,对应的需要使用multiprocessing提供的JoinableQueue模块,其使用过程和在线程中使用的queue方法相同。

3. 具体代码实现

对某某视频进行数据采集

import requests
import pymongo
import time
from multiprocessing import Process
from multiprocessing import JoinableQueue as Queue


class TenXun():
    client = pymongo.MongoClient(host='127.0.0.1', port=27017)
    collection = client['spiders']['tenxun']
    def __init__(self):
        self.headers = {
            "origin": "https://v.qq.com",
            "referer": "https://v.qq.com/",
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36',
        }

        self.url = 'https://pbaccess.video.qq.com/trpc.vector_layout.page_view.PageService/getPage?video_appid=3000010'
        self.data_queue = Queue()
        self.json_queue = Queue()
        self.content_list_queue = Queue()

    def get_url(self):
        for i in range(1, 10):
            data = {"page_context": {"page_index": str(i)},
                    "page_params": {"page_id": "channel_list_second_page", "page_type": "operation",
                                    "channel_id": "100113",
                                    "filter_params": "ifeature=-1&iarea=-1&iyear=-1&ipay=-1&sort=75", "page": str(i)},
                    "page_bypass_params": {"params": {"page_id": "channel_list_second_page", "page_type": "operation",
                                                      "channel_id": "100113",
                                                      "filter_params": "ifeature=-1&iarea=-1&iyear=-1&ipay=-1&sort=75",
                                                      "page": str(i), "caller_id": "3000010", "platform_id": "2",
                                                      "data_mode": "default", "user_mode": "default"},
                                           "scene": "operation", "abtest_bypass_id": "4610e3d06ca518f3"}}
            self.data_queue.put(data)

    def get_data(self):
        while True:
            data = self.data_queue.get()
            # print(url)
            response = requests.get(self.url, headers=self.headers, json=data)
            # print(response.json())
            self.json_queue.put(response.json())
            self.data_queue.task_done()

    def parse_data(self):
        while True:
            data = self.json_queue.get()
            self.json_queue.task_done()
            # print(data)
            cards = data['data']['CardList'][0]['children_list']['list']['cards']
            for video in cards:
                # print(video)
                item = {}
                item['second_title'] = video['params'].get('second_title') if video['params'].get('second_title') else '空'
                item['title'] = video['params']['title']
                item['timelong'] = video['params'].get('timelong') if video['params'].get('timelong') else '空'
                item['opinion_score'] = video['params'].get('opinion_score') if video['params'].get('opinion_score') else '空'
                # print(item)
                self.content_list_queue.put(item)


    def save_data(self):

        while True:
            item = self.content_list_queue.get()
            print(item)
            self.collection.insert_one(item)
            self.content_list_queue.task_done()

    def main(self):
        process_list = []
        # 1. url_list
        t_url = Process(target=self.get_url)
        process_list.append(t_url)
        # 2. 遍历,发送请求
        for i in range(5):  # 创建5个子进程
            t_parse = Process(target=self.get_data)
            process_list.append(t_parse)
        # 3. 提取数据
        t_content = Process(target=self.parse_data)
        process_list.append(t_content)
        # 4. 保存

        t_save = Process(target=self.save_data)
        process_list.append(t_save)
        for t in process_list:
            # print(t)
            t.daemon = True  # 把进程设置为守护线程,主进程结束,子进程结束
            t.start()
            time.sleep(0.2)

        for q in [self.data_queue, self.json_queue, self.content_list_queue]:
            q.join()  # 让主线程阻塞,等待队列的计数为0,

        print("主进程结束")


if __name__ == '__main__':
    t1 = time.time()
    tx = TenXun()
    tx.main()
    print("total cost:", time.time() - t1)

注意

  • 上述多进程实现的代码中,multiprocessing提供的JoinableQueue可以创建可连接的共享进程队列。和普通的Queue对象一样,队列允许项目的使用者通知生产者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。 对应的该队列能够和普通队列一样能够调用task_done和join方法

  • 初始化mongo可能会引起:TypeError: cannot pickle ‘_thread.lock’ object

五. 异步协程爬虫实现

  • 爬虫是 IO 密集型任务,比如如果我们使用 requests 库来爬取某个站点的话,发出一个请求之后,程序必须要等待网站返回响应之后才能接着运行,而在等待响应的过程中,整个爬虫程序是一直在等待的,实际上没有做任何的事情。对于这种情况我们有没有优化方案呢?

1. 基本概念

异步
为完成某个任务,不同程序单元之间过程中无需通信协调,也能完成任务的方式,不相关的程序单元之间可以是异步的。

例如,爬虫下载网页。调度程序调用下载程序后,即可调度其他任务,而无需与该下载任务保持通信以协调行为。不同网页的下载、保存等操作都是无关的,也无需相互通知协调。这些异步操作的完成时刻并不确定。

同步

不同程序单元为了完成某个任务,在执行过程中需靠某种通信方式以协调一致,我们称这些程序单元是同步执行的。

阻塞

阻塞状态指程序未得到所需计算资源时被挂起的状态。程序在等待某个操作完成期间,自身无法继续处理其他的事情,则称该程序在该操作上是阻塞的。

非阻塞

程序在等待某操作过程中,自身不被阻塞,可以继续处理其他的事情,则称该程序在该操作上是非阻塞的。

同步/异步关注的是消息通信机制 (synchronous communication/ asynchronouscommunication) 。

阻塞/非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

2. 协程异步实现方法

  • aiohttp是一个为Python提供异步HTTP 客户端/服务端编程,基于asyncio(Python用于支持异步编程的标准库)的异步库。asyncio可以实现单线程并发IO操作,其实现了TCP、UDP、SSL等协议,aiohttp就是基于asyncio实现的http框架。

  • async 用来声明一个函数为异步函数

  • await 用来声明程序挂起,比如异步程序执行到某一步时需要等待的时间很长,就将此挂起,去执行其他的异步程序

2.1 aiohttp的使用

  • 使用方式和requests基本保持一致
  • requests使用代理是proxies,aiohttp是proxy
  • aiohttp获取进制数据是read()
  • 文档:https://aiohttp.readthedocs.io/

3. 同步异步简单对比

同步

import time
import requests
def main():
    for i in range(30):
        res = requests.get('https://www.baidu.com')
        print(f'第{i + 1}次请求,status_code = {res.status_code}')

if __name__ == '__main__':
    start = time.time()
    main()
    end = time.time()
    print(f'同步发送30次请求,耗时:{end - start}')

异步

import asyncio
import aiohttp


async def requests_data(client,i):
    res = await client.get('https://www.baidu.com')
    print(f'第{i + 1}次请求,status_code = {res.status}')
    # await asyncio.sleep(1)
    return res


async def main():
    # 生明一个异步的上下文管理器,能帮助我们自己的分配和释放资源
    # aiohttp.ClientSession()   类似requests的sessi()
    async with aiohttp.ClientSession() as client:
        task_list = []
        for i in range(30):
            # 获取到协程对象
            res = requests_data(client, i)
            # 创建task对象
            task = asyncio.create_task(res)
            task_list.append(task)
            # 直接执行异步对象任务,会阻塞
            # await requests_data(client, i)
        # 等待执行的异步 将task对象交有event_loop来控制
        done, pending = await asyncio.wait(task_list)
        print(done, pending)
        for item in done:
            print(item.result())




if __name__ == '__main__':
    start = time.time()
    # 开启事件循环对象
    loop = asyncio.get_event_loop()
    # 用事件循环对象开启协程异步对象
    loop.run_until_complete(main())
    end = time.time()
    print(f'同步发送30次请求,耗时:{end - start}')

4. 具体代码实现

对某某荣耀官网所以的图片信息进行采集

import os
import requests
import asyncio  # asyncio是Python3.4引入的一个标准库,直接内置了对异步IO的支持。asyncio模块提供了使用协程构建并发应用的工具
import aiohttp  # 异步请求库aiohttp 加快图片 url 的网页请求
import time

class Crawl_Image:
    def __init__(self):
        self.skin_url = 'https://game.gtimg.cn/images/yxzj/img201606/skin/hero-info/{}/{}-bigskin-{}.jpg'
        self.url = 'https://pvp.qq.com/web201605/js/herolist.json'
        self.headers = {
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36',
        }

    async def download_image(self, session, ename, cname):
        for i in range(1, 10):
            response = await session.get(self.skin_url.format(ename, ename, i), headers=self.headers)
            # status获取到状态码
            if response.status == 200:
                # read 获取到进制数据
                content = await response.read()
                with open("图片/" + cname + "-" + str(i) + '.jpg', 'wb') as f:
                    f.write(content)
                print('下载{}第{}张图片成功'.format(cname, str(i)))
            else:
                break


    async def run(self):

        async with aiohttp.ClientSession() as session:
            response = await session.get(self.url, headers=self.headers)
            wzry_data = await response.json(content_type=None)
            tasks = []
            for i in wzry_data:
                ename = i['ename']
                cname = i['cname']
                # 获取协程对象
                res = self.download_image(session, ename, cname)
                # 将协程对象转换成task对象 才能做到异步
                task = asyncio.create_task(res)
                tasks.append(task)
            # 等待执行的异步 将task对象交由event_loop来控制
            await asyncio.wait(tasks)


if __name__ == '__main__':
    if not os.path.exists('图片'):
        os.mkdir('图片')
    start = time.time()
    crawl_image = Crawl_Image()
    # 获取事件循环 Eventloop 我们想运用协程,首先要生成一个loop对象,然后loop.run_xxx()就可以运行协程了,而如何创建这个loop, 方法有两种:对于主线程是loop=get_event_loop().
    loop = asyncio.get_event_loop()
    # 执行协程
    loop.run_until_complete(crawl_image.run())
    print('运行时间{}'.format(time.time() - start))

4. 1 aiomysql的使用

  • 利用python3中新加入的异步关键词 async/await , 我们使用各种异步操作为来执行各种异步的操作,如使用 aiohttp 来代替 requests 来执行异步的网络请求操作,使用 motor 来代替同步的 pymongo 库来操作mongo数据库,同样,我们在开发同步的python程序时,我们会使用PyMySQL来操作mysql数据库,同样,我们会使用aiomysql来异步操作mysql 数据库。

使用方式

import asyncio
import aiomysql

loop = asyncio.get_event_loop()


async def test_example():
    conn = await aiomysql.connect(host='127.0.0.1', port=3306,
                                       user='root', password='root', db='spiders',
                                       loop=loop)

    cur = await conn.cursor()
    await cur.execute("SELECT * from tx")
    print(cur.description)
    r = await cur.fetchall()
    print(r)
    await cur.close()
    conn.close()

loop.run_until_complete(test_example())

写在最后:
本人写作水平有限,如有讲解不到位或者讲解错误的地方,还请各位大佬在评论区多多指教,共同进步.如有需要代码和讲解交流,可以加本人微信18847868809

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet.Applet 简单实现!~ 网页表格组件 GWT Advanced Table GWT Advanced Table 是一个基于 GWT 框架的网页表格组件,可实现分页数据显示、数据排序和过滤等功能! Google Tag Library 该标记库和 Google 有关。使用该标记库,利用 Google 为你的网站提供网站查询,并且可以直接在你的网页里面显示搜查的结果。 github-java-api github-java-api 是 Github 网站 API 的 Java 语言版本。 java缓存工具 SimpleCache SimpleCache 是一个简单易用的java缓存工具,用来简化缓存代码的编写,让你摆脱单调乏味的重复工作!1. 完全透明的缓存支持,对业务代码零侵入 2. 支持使用Redis和Memcached作为后端缓存。3. 支持缓存数据分区规则的定义 4. 使用redis作缓存时,支持list类型的高级数据结构,更适合论坛帖子列表这种类型的数据 5. 支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 Java对象的SQL接口 JoSQL JoSQL(SQLforJavaObjects)为Java开发者提供运用SQL语句来操作Java对象集的能力.利用JoSQL可以像操作数据库中的数据一样对任何Java对象集进行查询,排序,分组。 搜索自动提示 Autotips AutoTips是为解决应用系统对于【自动提示】的需要(如:Google搜索), 而开发的架构无关的公共控件, 以满足该类需求可以通过快速配置来开发。AutoTips基于搜索引擎Apache Lucene实现。AutoTips提供统一UI。 WAP浏览器 j2wap j2wap 是一个基于Java的WAP浏览器,目前处于BETA测试阶段。它支持WAP 1.2规范,除了WTLS 和WBMP。 Java注册表操作类 jared jared是一个用来操作Windows注册表的 Java 类库,你可以用来对注册表信息进行读写。 GIF动画制作工具 GiftedMotion GiftedMotion是一个很小的,免费而且易于使用图像互换格式动画是能够设计一个有趣的动画了一系列的数字图像。使用简便和直截了当,用户只需要加载的图片和调整帧您想要的,如位置,时间显示和处理方法前帧。 Java的PList类库 Blister Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端 JOpenID JOpenID是一个轻量级的OpenID 2.0 Java客户端,仅50KB+(含源代码),允许任何Web网站通过OpenID支持用户直接登录而无需注册,例如Google Account或Yahoo Account。 JActor的文件持久化组件 JFile JFile 是 JActor 的文件持久化组件,以及一个高吞吐量的可靠事务日志组件。 Google地图JSP标签库 利用Google:maps JSP标签库就能够在你的Web站点上实现GoogleMaps的所有功能而且不需要javascript或AJAX编程。它还能够与JSTL相结合生成数据库驱动的动态Maps。 OAuth 实现框架 Agorava Agorava 是一个实现了 OAuth 1.0a 和 OAuth 2.0 的框架,提供了简单的方式通过社交媒体进行身份认证的功能。 Eclipse的JavaScript插件 JSEditor JSEditor 是 Eclipse 下编辑 JavaScript 源码的插件,提供语法高亮以及一些通用的面向对象方法。 Java数据库连接池 BoneCP BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K
Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet.Applet 简单实现!~ 网页表格组件 GWT Advanced Table GWT Advanced Table 是一个基于 GWT 框架的网页表格组件,可实现分页数据显示、数据排序和过滤等功能! Google Tag Library 该标记库和 Google 有关。使用该标记库,利用 Google 为你的网站提供网站查询,并且可以直接在你的网页里面显示搜查的结果。 github-java-api github-java-api 是 Github 网站 API 的 Java 语言版本。 java缓存工具 SimpleCache SimpleCache 是一个简单易用的java缓存工具,用来简化缓存代码的编写,让你摆脱单调乏味的重复工作!1. 完全透明的缓存支持,对业务代码零侵入 2. 支持使用Redis和Memcached作为后端缓存。3. 支持缓存数据分区规则的定义 4. 使用redis作缓存时,支持list类型的高级数据结构,更适合论坛帖子列表这种类型的数据 5. 支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 Java对象的SQL接口 JoSQL JoSQL(SQLforJavaObjects)为Java开发者提供运用SQL语句来操作Java对象集的能力.利用JoSQL可以像操作数据库中的数据一样对任何Java对象集进行查询,排序,分组。 搜索自动提示 Autotips AutoTips是为解决应用系统对于【自动提示】的需要(如:Google搜索), 而开发的架构无关的公共控件, 以满足该类需求可以通过快速配置来开发。AutoTips基于搜索引擎Apache Lucene实现。AutoTips提供统一UI。 WAP浏览器 j2wap j2wap 是一个基于Java的WAP浏览器,目前处于BETA测试阶段。它支持WAP 1.2规范,除了WTLS 和WBMP。 Java注册表操作类 jared jared是一个用来操作Windows注册表的 Java 类库,你可以用来对注册表信息进行读写。 GIF动画制作工具 GiftedMotion GiftedMotion是一个很小的,免费而且易于使用图像互换格式动画是能够设计一个有趣的动画了一系列的数字图像。使用简便和直截了当,用户只需要加载的图片和调整帧您想要的,如位置,时间显示和处理方法前帧。 Java的PList类库 Blister Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端 JOpenID JOpenID是一个轻量级的OpenID 2.0 Java客户端,仅50KB+(含源代码),允许任何Web网站通过OpenID支持用户直接登录而无需注册,例如Google Account或Yahoo Account。 JActor的文件持久化组件 JFile JFile 是 JActor 的文件持久化组件,以及一个高吞吐量的可靠事务日志组件。 Google地图JSP标签库 利用Google:maps JSP标签库就能够在你的Web站点上实现GoogleMaps的所有功能而且不需要javascript或AJAX编程。它还能够与JSTL相结合生成数据库驱动的动态Maps。 OAuth 实现框架 Agorava Agorava 是一个实现了 OAuth 1.0a 和 OAuth 2.0 的框架,提供了简单的方式通过社交媒体进行身份认证的功能。 Eclipse的JavaScript插件 JSEditor JSEditor 是 Eclipse 下编辑 JavaScript 源码的插件,提供语法高亮以及一些通用的面向对象方法。 Java数据库连接池 BoneCP BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K
Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet.Applet 简单实现!~ 网页表格组件 GWT Advanced Table GWT Advanced Table 是一个基于 GWT 框架的网页表格组件,可实现分页数据显示、数据排序和过滤等功能! Google Tag Library 该标记库和 Google 有关。使用该标记库,利用 Google 为你的网站提供网站查询,并且可以直接在你的网页里面显示搜查的结果。 github-java-api github-java-api 是 Github 网站 API 的 Java 语言版本。 java缓存工具 SimpleCache SimpleCache 是一个简单易用的java缓存工具,用来简化缓存代码的编写,让你摆脱单调乏味的重复工作!1. 完全透明的缓存支持,对业务代码零侵入 2. 支持使用Redis和Memcached作为后端缓存。3. 支持缓存数据分区规则的定义 4. 使用redis作缓存时,支持list类型的高级数据结构,更适合论坛帖子列表这种类型的数据 5. 支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 Java对象的SQL接口 JoSQL JoSQL(SQLforJavaObjects)为Java开发者提供运用SQL语句来操作Java对象集的能力.利用JoSQL可以像操作数据库中的数据一样对任何Java对象集进行查询,排序,分组。 搜索自动提示 Autotips AutoTips是为解决应用系统对于【自动提示】的需要(如:Google搜索), 而开发的架构无关的公共控件, 以满足该类需求可以通过快速配置来开发。AutoTips基于搜索引擎Apache Lucene实现。AutoTips提供统一UI。 WAP浏览器 j2wap j2wap 是一个基于Java的WAP浏览器,目前处于BETA测试阶段。它支持WAP 1.2规范,除了WTLS 和WBMP。 Java注册表操作类 jared jared是一个用来操作Windows注册表的 Java 类库,你可以用来对注册表信息进行读写。 GIF动画制作工具 GiftedMotion GiftedMotion是一个很小的,免费而且易于使用图像互换格式动画是能够设计一个有趣的动画了一系列的数字图像。使用简便和直截了当,用户只需要加载的图片和调整帧您想要的,如位置,时间显示和处理方法前帧。 Java的PList类库 Blister Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端 JOpenID JOpenID是一个轻量级的OpenID 2.0 Java客户端,仅50KB+(含源代码),允许任何Web网站通过OpenID支持用户直接登录而无需注册,例如Google Account或Yahoo Account。 JActor的文件持久化组件 JFile JFile 是 JActor 的文件持久化组件,以及一个高吞吐量的可靠事务日志组件。 Google地图JSP标签库 利用Google:maps JSP标签库就能够在你的Web站点上实现GoogleMaps的所有功能而且不需要javascript或AJAX编程。它还能够与JSTL相结合生成数据库驱动的动态Maps。 OAuth 实现框架 Agorava Agorava 是一个实现了 OAuth 1.0a 和 OAuth 2.0 的框架,提供了简单的方式通过社交媒体进行身份认证的功能。 Eclipse的JavaScript插件 JSEditor JSEditor 是 Eclipse 下编辑 JavaScript 源码的插件,提供语法高亮以及一些通用的面向对象方法。 Java数据库连接池 BoneCP BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K
Spring4GWT GWT Spring 使得在 Spring 框架下构造 GWT 应用变得很简单,提供一个易于理解的依赖注入和RPC机制。 Java扫雷游戏 JVMine JVMine用Applets开发的扫雷游戏,可在线玩。 public class JVMine extends java.applet.Applet 简单实现!~ 网页表格组件 GWT Advanced Table GWT Advanced Table 是一个基于 GWT 框架的网页表格组件,可实现分页数据显示、数据排序和过滤等功能! Google Tag Library 该标记库和 Google 有关。使用该标记库,利用 Google 为你的网站提供网站查询,并且可以直接在你的网页里面显示搜查的结果。 github-java-api github-java-api 是 Github 网站 API 的 Java 语言版本。 java缓存工具 SimpleCache SimpleCache 是一个简单易用的java缓存工具,用来简化缓存代码的编写,让你摆脱单调乏味的重复工作!1. 完全透明的缓存支持,对业务代码零侵入 2. 支持使用Redis和Memcached作为后端缓存。3. 支持缓存数据分区规则的定义 4. 使用redis作缓存时,支持list类型的高级数据结构,更适合论坛帖子列表这种类型的数据 5. 支持混合使用redis缓存和memcached缓存。可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 Java对象的SQL接口 JoSQL JoSQL(SQLforJavaObjects)为Java开发者提供运用SQL语句来操作Java对象集的能力.利用JoSQL可以像操作数据库中的数据一样对任何Java对象集进行查询,排序,分组。 搜索自动提示 Autotips AutoTips是为解决应用系统对于【自动提示】的需要(如:Google搜索), 而开发的架构无关的公共控件, 以满足该类需求可以通过快速配置来开发。AutoTips基于搜索引擎Apache Lucene实现。AutoTips提供统一UI。 WAP浏览器 j2wap j2wap 是一个基于Java的WAP浏览器,目前处于BETA测试阶段。它支持WAP 1.2规范,除了WTLS 和WBMP。 Java注册表操作类 jared jared是一个用来操作Windows注册表的 Java 类库,你可以用来对注册表信息进行读写。 GIF动画制作工具 GiftedMotion GiftedMotion是一个很小的,免费而且易于使用图像互换格式动画是能够设计一个有趣的动画了一系列的数字图像。使用简便和直截了当,用户只需要加载的图片和调整帧您想要的,如位置,时间显示和处理方法前帧。 Java的PList类库 Blister Blister是一个用于操作苹果二进制PList文件格式的Java开源类库(可用于发送数据给iOS应用程序)。 重复文件检查工具 FindDup.tar FindDup 是一个简单易用的工具,用来检查计算机上重复的文件。 OpenID的Java客户端 JOpenID JOpenID是一个轻量级的OpenID 2.0 Java客户端,仅50KB+(含源代码),允许任何Web网站通过OpenID支持用户直接登录而无需注册,例如Google Account或Yahoo Account。 JActor的文件持久化组件 JFile JFile 是 JActor 的文件持久化组件,以及一个高吞吐量的可靠事务日志组件。 Google地图JSP标签库 利用Google:maps JSP标签库就能够在你的Web站点上实现GoogleMaps的所有功能而且不需要javascript或AJAX编程。它还能够与JSTL相结合生成数据库驱动的动态Maps。 OAuth 实现框架 Agorava Agorava 是一个实现了 OAuth 1.0a 和 OAuth 2.0 的框架,提供了简单的方式通过社交媒体进行身份认证的功能。 Eclipse的JavaScript插件 JSEditor JSEditor 是 Eclipse 下编辑 JavaScript 源码的插件,提供语法高亮以及一些通用的面向对象方法。 Java数据库连接池 BoneCP BoneCP 是一个高性能的开源java数据库连接池实现库。它的设计初衷就是为了提高数据库连接池的性能,根据某些测试数据发现,BoneCP是最快的连接池。BoneCP很小,只有四十几K
你可以使用多进程多线程来协调爬虫任务。下面是一个简单的示例,展示了如何使用Python的multiprocessing和threading模块来实现: ```python import requests import multiprocessing import threading def crawl(url): response = requests.get(url) # 处理爬取的数据 print(response.text) def multi_threading_crawler(urls): # 创建线程池 thread_pool = [] for url in urls: thread = threading.Thread(target=crawl, args=(url,)) thread.start() thread_pool.append(thread) # 等待所有线程完成 for thread in thread_pool: thread.join() def multi_process_crawler(urls): # 创建进程池 process_pool = [] for url in urls: process = multiprocessing.Process(target=multi_threading_crawler, args=([url],)) process.start() process_pool.append(process) # 等待所有进程完成 for process in process_pool: process.join() if __name__ == '__main__': urls = ['http://example.com', 'http://example.org', 'http://example.net'] multi_process_crawler(urls) ``` 在这个例子中,我们首先定义了一个`crawl`函数,它接受一个URL作为参数,并使用`requests`库来发起HTTP请求并处理响应数据。 然后,我们定义了`multi_threading_crawler`函数,它接受一个URL列表作为参数,并创建一个线程池,每个线程使用`crawl`函数来爬取一个URL。 最后,我们定义了`multi_process_crawler`函数,它接受一个URL列表作为参数,并创建一个进程池,每个进程使用`multi_threading_crawler`函数来启动一个爬虫线程池。 在主程序中,我们定义了一个URL列表,并调用`multi_process_crawler`函数来启动爬虫进程池。 这样,每个URL将被不同的进程处理,并且每个进程内部会创建一个线程池来并发爬取URL。这种方式可以充分利用计算机的多核和多线程资源,提高爬虫的效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值