Python 爬虫获取某贴吧所有成员用户名

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
日志服务 SLS,月写入数据量 50GB 1个月
简介: 最近想用Python爬虫搞搞百度贴吧的操作,所以我得把原来申请的小号找出来用。有一个小号我忘了具体ID,只记得其中几个字母以及某个加入的贴吧。所以今天就用爬虫来获取C语言贴吧的所有成员。

最近想用Python爬虫搞搞百度贴吧的操作,所以我得把原来申请的小号找出来用。有一个小号我忘了具体ID,只记得其中几个字母以及某个加入的贴吧。所以今天就用爬虫来获取C语言贴吧的所有成员。

计划很简单,爬百度贴吧的会员页面,把结果存到MySQL数据库中,等到所有会员都爬完之后。我就可以使用简单的SQL语句查询账号名了。由于C语言贴吧会员有50多万,所以我还需要在合适的时候(例如插入数据库失败)把错误信息打印到日志文件中。由于我是Python新手,所以就不弄什么多线程得了,直接一个脚本用到黑。

看着很简单,实际也很简单。写完了我看了一下,用到的知识只有最基础的SQL操作、BeautifulSoup解析。

首先第一步就是看一下这个吧的信息页有多少页,关键代码如下。踩了两天坑,总算感觉对BeautifulSoup熟悉了一点。代码也很简单,按照class名查找到总页数这个标签,然后用正则表达式匹配到页数数字。这里要说一下,正则表达式的分组真好用。以前偷懒只学了一点正则表达式,发现没啥作用,只有配合分组才能比较精确的查找字符。

    html = request.urlopen(base_url).read().decode(encoding)
    soup = BeautifulSoup(html, 'lxml')
    page_span = soup.find('span', class_='tbui_total_page')
    p = re.compile(r'共(\d+)页')
    result = p.match(page_span.string)
    global total_pages
    total_pages = int(result.group(1))

    logger.info(f'会员共{total_pages}页')

有了总页数,我们就可以遍历页面了,代码如下。写的虽然比较脏,但是能用就行了,大家嫌难看就难看吧。这里做的事情就很简单了,从第一页开始遍历,一直遍历到最后一页。把每一页的用户名字提取出来,然后用_insert_table(connection, name)函数存到MySQL中。

因为我为了省事,直接把百度用户名当做主键了。但是保不齐贴吧有什么bug,导致用户名重复之类的问题,导致插入失败。所以我用try把保存这一块包起来。有异常的话就打印到日志中,方便排查。日志分成两种级别的,INFO级别输出到控制台,ERROR级别输出到文件。

def _find_all_users():
    global connection
    for i in range(start_page, total_pages + 1):
        target_url = f'{base_url}&pn={i}'
        logger.info(f'正在分析第{i}页')
        html = request.urlopen(target_url).read().decode(encoding)
        soup = BeautifulSoup(html, 'lxml')
        outer_div = soup.find('div', class_='forum_info_section member_wrap clearfix bawu-info')
        inner_spans = outer_div.find_all('span', class_='member')
        for index, span in enumerate(inner_spans):
            name_link = span.find('a', class_='user_name')
            name = name_link.string
            logger.info(f'已找到 {name}')

            try:
                _insert_table(connection, name)
            except:
                logger.error(f'第{i}页{index}第个用户 {name} 发生异常')

完整的代码见下。

"""
Python写的百度贴吧工具
"""
import pymysql

host = 'localhost'
db_name = 'tieba'
username = 'root'
password = '12345678'


def _get_connection(host, username, password, db_name):
    return pymysql.connect(host=host,
                           user=username,
                           password=password,
                           charset='utf8mb4',
                           db=db_name)


def _create_table(connection):
    create_table_sql = """
    CREATE TABLE tieba_member(
    username CHAR(255) PRIMARY KEY 
    )
    """
    with connection.cursor() as cursor:
        cursor.execute(create_table_sql)
        connection.commit()


def _insert_table(connection, username):
    insert_table_sql = """
    INSERT INTO tieba_member 
    VALUES(%s)"""

    with connection.cursor() as cursor:
        cursor.execute(insert_table_sql, (username,))
        connection.commit()


import urllib.request as request
from bs4 import BeautifulSoup
import re
import tieba.log_config
import logging

logger = logging.getLogger()

encoding = 'GBK'

base_url = 'http://tieba.baidu.com/bawu2/platform/listMemberInfo?word=c%D3%EF%D1%D4'
# base_url = 'http://tieba.baidu.com/bawu2/platform/listMemberInfo?word=%B9%FD%C1%CB%BC%B4%CA%C7%BF%CD'
start_page = 1
total_pages = None

connection = _get_connection(host, username, password, db_name)


def _get_total_pages():
    html = request.urlopen(base_url).read().decode(encoding)
    soup = BeautifulSoup(html, 'lxml')
    page_span = soup.find('span', class_='tbui_total_page')
    p = re.compile(r'共(\d+)页')
    result = p.match(page_span.string)
    global total_pages
    total_pages = int(result.group(1))

    logger.info(f'会员共{total_pages}页')


def _find_all_users():
    global connection
    for i in range(start_page, total_pages + 1):
        target_url = f'{base_url}&pn={i}'
        logger.info(f'正在分析第{i}页')
        html = request.urlopen(target_url).read().decode(encoding)
        soup = BeautifulSoup(html, 'lxml')
        outer_div = soup.find('div', class_='forum_info_section member_wrap clearfix bawu-info')
        inner_spans = outer_div.find_all('span', class_='member')
        for index, span in enumerate(inner_spans):
            name_link = span.find('a', class_='user_name')
            name = name_link.string
            logger.info(f'已找到 {name}')

            try:
                _insert_table(connection, name)
            except:
                logger.error(f'第{i}页{index}第个用户 {name} 发生异常')


import datetime

if __name__ == '__main__':
    _get_total_pages()
    _find_all_users()

还有另一个文件用来配置日志的。你也可以把这两个文件合在一起,只不过看着可能更乱了。

import logging

# 创建Logger
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)

# 创建Handler

# 终端Handler
consoleHandler = logging.StreamHandler()
consoleHandler.setLevel(logging.DEBUG)

# 文件Handler
fileHandler = logging.FileHandler('log.log', mode='a', encoding='UTF-8')
fileHandler.setLevel(logging.ERROR)

# Formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
consoleHandler.setFormatter(formatter)
fileHandler.setFormatter(formatter)

# 添加到Logger中
logger.addHandler(consoleHandler)
logger.addHandler(fileHandler)


性能测试

当然由于要爬的数据量比较大,我们还要计算一下可能的运行时间。首先不考虑爬虫被百度封了的情况。我把代码稍作修改,设定只爬前100页。

import datetime

if __name__ == '__main__':
    # _get_total_pages()
    total_pages = 100
    time1 = datetime.datetime.today()

    _find_all_users()
    time2 = datetime.datetime.today()
    print(time2)
    print(time1)
    print(time2 - time1)

结果如下,用时将近两分钟。做了简单计算得出结论,要爬完c语言贴吧的52万个会员,需要将近7个小时。所以程序还需要改进。

2017-04-04 23:57:59.197993
2017-04-04 23:56:10.064666
0:01:49.133327

首先先从数据库方面考虑一下。Windows下MySQL默认的数据库引擎是Innodb,特点是支持事务管理、外键、行级锁,但是相应的速度比较慢。我把表重新建为MyISAM类型的。然后重新运行一下测试,看看这次速度会不会有变化。

CREATE TABLE tieba_member (
  username CHAR(255) PRIMARY KEY
)
  ENGINE = MyISAM

这次性能提升的有点快,速度足足提高了76%。可见默认的并不一定是最好的。

2017-04-05 00:15:19.989766
2017-04-05 00:14:53.407476
0:00:26.582290

既然都开始测试了,不妨干脆点。MySQL还有一种引擎是Memory,直接把数据放到内存中。速度肯定会更快!不过测试结果很遗憾,还是26秒。可见数据库这方面的优化到头了。

CREATE TABLE tieba_member (
  username CHAR(255) PRIMARY KEY
)
  ENGINE = MEMORY

不过性能确实提高了很多。经过计算,这次只需要一个半小时即可爬完52万个用户。如果在开多个进程,相信速度还会更快。所以这篇文章就差不多完成了。等明天爬完之后,我把结果更新一下,任务就真正完成了!

不过结果很遗憾,爬虫失败了。为了速度更快我开了4个进程,分别爬1-5000页,5001-10000页,10001-15000页,以及15000-到最后4部分。
但是日志输出显示出现很多重复的用户名,5000页之后的用户名竟然和第一页相同。我百思不得其解,在使用浏览器测试发现,不知道是百度的防爬虫机制还是bug之类的,浏览器只能显示到450多页,在往后就会显示为空页面,如果页数更大,就一直返回第一页的内容。因此依赖于这个页面的贴吧爬虫宣布失败。

虽然失败了,但是还是学习到了不少经验。我测试了一下爬前450页,仅用时44秒。说明爬虫速度倒是还星还行。

import datetime
from multiprocessing import Process

if __name__ == '__main__':

    total_pages = _get_total_pages()

    processes = []
    processes.append(Process(target=_find_all_users, args=(1, 150)))
    processes.append(Process(target=_find_all_users, args=(151, 300)))
    processes.append(Process(target=_find_all_users, args=(301, 450)))

    time1 = datetime.datetime.today()
    for process in processes:
        process.start()

    for process in processes:
        process.join()

    time2 = datetime.datetime.today()
    print(f'开始时间{time1}')
    print(f'结束时间{time2}')
    print(f'用时{time2 - time1}')
相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
7天前
|
数据采集 存储 JSON
Python网络爬虫:Scrapy框架的实战应用与技巧分享
【10月更文挑战第27天】本文介绍了Python网络爬虫Scrapy框架的实战应用与技巧。首先讲解了如何创建Scrapy项目、定义爬虫、处理JSON响应、设置User-Agent和代理,以及存储爬取的数据。通过具体示例,帮助读者掌握Scrapy的核心功能和使用方法,提升数据采集效率。
46 6
|
1天前
|
数据采集 存储 JSON
Python爬虫开发中的分析与方案制定
Python爬虫开发中的分析与方案制定
|
6天前
|
数据采集 JSON 测试技术
Python爬虫神器requests库的使用
在现代编程中,网络请求是必不可少的部分。本文详细介绍 Python 的 requests 库,一个功能强大且易用的 HTTP 请求库。内容涵盖安装、基本功能(如发送 GET 和 POST 请求、设置请求头、处理响应)、高级功能(如会话管理和文件上传)以及实际应用场景。通过本文,你将全面掌握 requests 库的使用方法。🚀🌟
27 7
|
8天前
|
数据采集 Web App开发 前端开发
Python爬虫进阶:Selenium在动态网页抓取中的实战
【10月更文挑战第26天】动态网页抓取是网络爬虫的难点,因为数据通常通过JavaScript异步加载。Selenium通过模拟浏览器行为,可以加载和执行JavaScript,从而获取动态网页的完整内容。本文通过实战案例,介绍如何使用Selenium在Python中抓取动态网页。首先安装Selenium库和浏览器驱动,然后通过示例代码展示如何抓取英国国家美术馆的图片信息。
29 6
|
5天前
|
数据采集 Web App开发 JavaScript
爬虫策略规避:Python爬虫的浏览器自动化
爬虫策略规避:Python爬虫的浏览器自动化
|
6天前
|
数据采集 存储 XML
Python实现网络爬虫自动化:从基础到实践
本文将介绍如何使用Python编写网络爬虫,从最基础的请求与解析,到自动化爬取并处理复杂数据。我们将通过实例展示如何抓取网页内容、解析数据、处理图片文件等常用爬虫任务。
|
8天前
|
数据采集 前端开发 中间件
Python网络爬虫:Scrapy框架的实战应用与技巧分享
【10月更文挑战第26天】Python是一种强大的编程语言,在数据抓取和网络爬虫领域应用广泛。Scrapy作为高效灵活的爬虫框架,为开发者提供了强大的工具集。本文通过实战案例,详细解析Scrapy框架的应用与技巧,并附上示例代码。文章介绍了Scrapy的基本概念、创建项目、编写简单爬虫、高级特性和技巧等内容。
30 4
|
8天前
|
数据采集 Web App开发 iOS开发
如何利用 Python 的爬虫技术获取淘宝天猫商品的价格信息?
本文介绍了使用 Python 爬虫技术获取淘宝天猫商品价格信息的两种方法。方法一使用 Selenium 模拟浏览器操作,通过定位页面元素获取价格;方法二使用 Requests 和正则表达式直接请求页面内容并提取价格。每种方法都有详细步骤和代码示例,但需注意反爬措施和法律法规。
|
8天前
|
数据采集 存储 Web App开发
利用Python 的爬虫技术淘宝天猫销量和库存
使用 Python 爬虫技术获取淘宝天猫商品销量和库存的步骤包括:1. 安装 Python 和相关库(如 selenium、pandas),下载浏览器驱动;2. 使用 selenium 登录淘宝或天猫;3. 访问商品页面,分析网页结构,提取销量和库存信息;4. 处理和存储数据。注意网页结构可能变化,需遵守法律法规。
|
10天前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###