Python多线程爬取表情包的实现方法与技巧

多线程爬取表情包

有一个网站,叫做“斗图啦”,网址是:https://www.doutula.com/。这里面包含了许许多多的有意思的斗图图片,还蛮好玩的。有时候为了斗图要跑到这个上面来找表情,实在有点费劲。于是就产生了一个邪恶的想法,可以写个爬虫,把所有的表情都给爬下来。这个网站对于爬虫来讲算是比较友好了,他不会限制你的headers,不会限制你的访问频率(当然,作为一个有素质的爬虫工程师,爬完赶紧撤,不要把人家服务器搞垮了),不会限制你的 IP 地址,因此技术难度不算太高。但是有一个问题,因为这里要爬的是图片,而不是文本信息,所以采用传统的爬虫是可以完成我们的需求,但是因为是下载图片所以速度比较慢,可能要爬一两个小时都说不准。因此这里我们准备采用多线程爬虫,一下可以把爬虫的效率提高好几倍。

一、分析网站和爬虫准备工作:

构建所有页面 URL 列表:

这里我们要爬的页面不是“斗图啦”首页,而是最新表情页面https://www.doutula.com/photo/list/,这个页面包含了所有的表情图片,只是是按照时间来排序的而已。我们把页面滚动到最下面,可以看到这个最新表情使用的是分页,当我们点击第二页的时候,页面的URL变成了https://www.doutula.com/photo/list/?page=2,而我们再回到第一页的时候,page又变成了1,所以这个翻页的URL其实很简单,前面这一串https://www.doutula.com/photo/list/?page=都是固定的,只是后面跟的数字不一样而已。并且我们可以看到,这个最新表情总共是有 869 页,因此这里我们可以写个非常简单的代码,来构建一个从 1 到 869 的页面的URL列表:

# 全局变量,用来保存页面的 URL 的
PAGE_URL_LIST = []
BASE_PAGE_URL = 'https://www.doutula.com/photo/list/?page='
for x in range(1, 870):
    url = BASE_PAGE_URL + str(x)
    PAGE_URL_LIST.append(url)
获取一个页面中所有的表情图片链接:

我们已经拿到了所有页面的链接,但是还没有拿到每个页面中表情的链接。经过分析,我们可以知道,其实每个页面中表情的HTML元素构成都是一样的,因此我们只需要针对一个页面进行分析,其他页面按照同样的规则,就可以拿到所有页面的表情链接了。这里我们以第一页为例,跟大家讲解。首先在页面中右键->检查->Elements,然后点击Elements最左边的那个小光标,再把鼠标放在随意一个表情上,这样下面的代码就定位到这个表情所在的代码位置了: 01.png 可以看到,这个img标签的class是等于img-responsive lazy image_dtz,然后我们再定位其他表情的img标签,发现所有的表情的img标签,他的class都是img-responsive lazy image_dtz02.png 03.png

因此我们只要把数据从网上拉下来,然后再根据这个规则进行提取就可以了。这里我们使用了两个第三方库,一个是requests,这个库是专门用来做网络请求的。第二个库是bs4,这个库是专门用来把请求下来的数据进行分析和过滤用的,如果没有安装好这两个库的,可以使用以下代码进行安装(我使用的是 python2.7 的版本):

# 安装 requests
pip install requests
# 安装 bs4
pip install bs4
# 安装 lxml 解析引擎
pip install lxml

然后我们以第一个页面为例,跟大家讲解如何从页面中获取所有表情的链接:

# 导入 requests 库
import requests
# 从 bs4 中导入 BeautifulSoup
from bs4 import BeautifulSoup

第一页的链接

url = ‘https://www.doutula.com/photo/list/?page=1

请求这个链接

response = requests.get(url)

使用返回的数据,构建一个 BeautifulSoup 对象

soup = BeautifulSoup(response.content,‘lxml’)

获取所有 class='img-responsive lazy image_dtz’的 img 标签

img_list = soup.find_all(‘img’, attrs={‘class’: ‘img-responsive lazy image_dta’}) for img in img_list: # 因为 src 属性刚开始获取的是 loading 的图片,因此使用 data-original 比较靠谱 print img[‘data-original’]

这样我们就可以在控制台看到本页中所有的表情图片的链接就全部都打印出来了。

下载图片:

有图片链接后,还要对图片进行下载处理,这里我们以一张图片为例:http://ws2.sinaimg.cn/bmiddle/9150e4e5ly1fhpi3ysfocj205i04aa9z.jpg,来看看Python中如何轻轻松松下载一张图片:

import urllib
url = 'http://ws2.sinaimg.cn/bmiddle/9150e4e5ly1fhpi3ysfocj205i04aa9z.jpg'
urllib.urlretrieve(url,filename='test.jpg')

这样就可以下载一张图片了。

结合以上三部分内容:

以上三部分,分别对,如何构建所有页面的URL,一个页面中如何获取所有表情的链接以及下载图片的方法。接下来把这三部分结合在一起,就可以构建一个完整但效率不高的爬虫了:

# 导入 requests 库
import requests
# 从 bs4 中导入 BeautifulSoup
from bs4 import BeautifulSoup
import urllib
import os

全局变量,用来保存页面的 URL 的

PAGE_URL_LIST = [] BASE_PAGE_URL = ‘https://www.doutula.com/photo/list/?page=’ for x in range(1, 870): url = BASE_PAGE_URL + str(x) PAGE_URL_LIST.append(url)

for page_url in PAGE_URL_LIST: # 请求这个链接 response = requests.get(page_url) # 使用返回的数据,构建一个 BeautifulSoup 对象 soup = BeautifulSoup(response.content,‘lxml’) # 获取所有 class=‘img-responsive lazy image_dtz’的 img 标签 img_list = soup.find_all(‘img’, attrs={‘class’: ‘img-responsive lazy image_dta’}) for img in img_list: # 因为 src 属性刚开始获取的是 loading 的图片,因此使用 data-original 比较靠谱 src = img[‘data-original’] # 有些图片是没有 http 的,那么要加一个 http if not src.startswith(‘http’): src = ‘http:’+ src # 获取图片的名称 filename = src.split(’/’).pop() # 拼接完整的路径 path = os.path.join(‘images’,filename) urllib.urlretrieve(src,path)

以上这份代码。可以完整的运行了。但是效率不高,毕竟是在下载图片,要一个个排队下载。如果能够采用多线程,在一张图片下载的时候,就完全可以去请求其他图片,而不用继续等待了。因此效率比较高,以下将该例子改为多线程来实现。

二、多线程下载图片:

这里多线程我们使用的是Python自带的threading模块。并且我们使用了一种叫做生产者和消费者的模式,生产者专门用来从每个页面中获取表情的下载链接存储到一个全局列表中。而消费者专门从这个全局列表中提取表情链接进行下载。并且需要注意的是,在多线程中使用全局变量要用来保证数据的一致性。以下是多线程的爬虫代码(如果有看不懂的,可以看视频,讲解很仔细):

#encoding: utf-8

import urllib import threading from bs4 import BeautifulSoup import requests import os import time

表情链接列表

FACE_URL_LIST = []

页面链接列表

PAGE_URL_LIST = []

构建 869 个页面的链接

BASE_PAGE_URL = ‘https://www.doutula.com/photo/list/?page=’ for x in range(1, 870): url = BASE_PAGE_URL + str(x) PAGE_URL_LIST.append(url)

初始化锁

gLock = threading.Lock()

生产者,负责从每个页面中提取表情的 url

class Producer(threading.Thread): def run(self): while len(PAGE_URL_LIST) > 0: # 在访问 PAGE_URL_LIST 的时候,要使用锁机制 gLock.acquire() page_url = PAGE_URL_LIST.pop() # 使用完后要及时把锁给释放,方便其他线程使用 gLock.release() response = requests.get(page_url) soup = BeautifulSoup(response.content, ‘lxml’) img_list = soup.find_all(‘img’, attrs={‘class’: ‘img-responsive lazy image_dta’}) gLock.acquire() for img in img_list: src = img[‘data-original’] if not src.startswith(‘http’): src = ‘http:’+ src # 把提取到的表情 url,添加到 FACE_URL_LIST 中 FACE_URL_LIST.append(src) gLock.release() time.sleep(0.5)

消费者,负责从 FACE_URL_LIST 提取表情链接,然后下载

class Consumer(threading.Thread): def run(self): print ‘%s is running’ % threading.current_thread while True: # 上锁 gLock.acquire() if len(FACE_URL_LIST) == 0: # 不管什么情况,都要释放锁 gLock.release() continue else: # 从 FACE_URL_LIST 中提取数据 face_url = FACE_URL_LIST.pop() gLock.release() filename = face_url.split(’/’)[-1] path = os.path.join(‘images’, filename) urllib.urlretrieve(face_url, filename=path)

if name == ‘main’: # 2 个生产者线程,去从页面中爬取表情链接 for x in range(2): Producer().start()

# 5 个消费者线程,去从 FACE_URL_LIST 中提取下载链接,然后下载
for x in range(5):
    Consumer().start()

写在最后:

本教程采用多线程来完成表情的爬取,可以让爬取效率高出很多倍。Python的多线程虽然有GIL全局解释器锁,但在网络IO处理这一块表现还是很好的,不用在一个地方一直等待。以上这个例子就很好的说明了多线程的好处。另外,如果你对Python和爬虫或者框架有相关的兴趣,那么可以加入 QQ 群:482869582一起学习和讨论哦(本教程视频可以入群找我索取)。也可以关注本公众号,每天都有最新的干货哦~~ 0.jpg


Python多线程爬取表情包的实现方法与技巧

4 回复

老司机才用多线程,新司机都去看协程了→_→
有空可以对比一下效率


import requests
from bs4 import BeautifulSoup
from concurrent.futures import ThreadPoolExecutor
import os
import time

class EmojiSpider:
    def __init__(self, save_dir='emojis'):
        self.save_dir = save_dir
        os.makedirs(save_dir, exist_ok=True)
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
        }
    
    def fetch_page(self, url):
        """获取页面内容"""
        try:
            resp = requests.get(url, headers=self.headers, timeout=10)
            resp.raise_for_status()
            return resp.text
        except Exception as e:
            print(f"请求失败 {url}: {e}")
            return None
    
    def parse_emojis(self, html):
        """解析表情包链接 - 需要根据实际网站调整"""
        soup = BeautifulSoup(html, 'html.parser')
        emoji_links = []
        
        # 示例:查找所有图片标签
        for img in soup.find_all('img'):
            src = img.get('src') or img.get('data-src')
            if src and any(ext in src.lower() for ext in ['.jpg', '.png', '.gif']):
                if src.startswith('//'):
                    src = 'https:' + src
                elif src.startswith('/'):
                    src = 'https://example.com' + src  # 替换为实际域名
                emoji_links.append(src)
        return emoji_links
    
    def download_emoji(self, url):
        """下载单个表情包"""
        try:
            filename = os.path.join(self.save_dir, url.split('/')[-1])
            if os.path.exists(filename):
                print(f"已存在: {filename}")
                return
            
            resp = requests.get(url, headers=self.headers, timeout=15)
            resp.raise_for_status()
            
            with open(filename, 'wb') as f:
                f.write(resp.content)
            print(f"下载成功: {filename}")
            return filename
        except Exception as e:
            print(f"下载失败 {url}: {e}")
            return None
    
    def crawl(self, start_urls, max_workers=5):
        """多线程爬取主函数"""
        all_emoji_urls = []
        
        # 第一步:收集所有表情包链接
        print("正在收集表情包链接...")
        for url in start_urls:
            html = self.fetch_page(url)
            if html:
                emoji_urls = self.parse_emojis(html)
                all_emoji_urls.extend(emoji_urls)
        
        print(f"共找到 {len(all_emoji_urls)} 个表情包")
        
        # 第二步:多线程下载
        print("开始多线程下载...")
        start_time = time.time()
        
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            results = list(executor.map(self.download_emoji, all_emoji_urls))
        
        # 统计结果
        success_count = sum(1 for r in results if r is not None)
        elapsed = time.time() - start_time
        
        print(f"\n下载完成!成功: {success_count}/{len(all_emoji_urls)}")
        print(f"耗时: {elapsed:.2f}秒")
        print(f"保存目录: {os.path.abspath(self.save_dir)}")

# 使用示例
if __name__ == '__main__':
    # 替换为实际的表情包网站URL列表
    urls = [
        'https://example.com/emojis/page1',
        'https://example.com/emojis/page2'
    ]
    
    spider = EmojiSpider()
    spider.crawl(urls, max_workers=10)

核心要点:

  1. 线程池控制并发ThreadPoolExecutor 管理线程数量,避免创建过多线程
  2. 异常处理:每个网络请求都有 try-catch,防止单个失败影响整体
  3. 去重处理:检查文件是否已存在,避免重复下载
  4. 头部伪装:设置 User-Agent 模拟浏览器访问
  5. URL标准化:处理相对路径和协议相对URL

需要根据实际网站调整的地方:

  • parse_emojis() 方法中的选择器逻辑
  • URL 前缀补全(代码中的 example.com
  • 可能需要添加 referer、cookies 等反爬措施

一句话建议: 记得调整解析逻辑匹配目标网站结构,并合理设置线程数避免被封IP。

老哥,你下载完有没有图片下载不完整的情况?

所以。。。楼主爬的表情包呢😂

回到顶部