Python爬虫案例(多线程+消息队列初阶)
目录
相关库介绍
-
BeautifulSoup
- Beautiful Soup 是一个用于从HTML或XML文件中提取数据的Python库。它提供了一种非常方便的方式来浏览文档、搜索特定标签或内容,以及对标签进行修改。Beautiful Soup的主要作用是帮助解析和提取HTML/XML文档中的数据,使得在Python中处理Web数据变得更加简单。
- 常用方法:
BeautifulSoup(markup, 'html.parser')
: 创建一个 BeautifulSoup 对象,用于解析 HTML 或 XML 文档。markup
是要解析的字符串或文件对象。find(name, attrs, recursive, string)
: 查找文档中第一个符合条件的标签。find_all(name, attrs, recursive, string)
: 查找文档中所有符合条件的标签,返回一个列表。select(selector)
: 使用 CSS 选择器语法查找元素。tag.text
或tag.get_text()
: 获取标签内的文本内容,包括所有子孙节点的文本。tag.string
: 获取标签内的直系文本内容,如果有多个子节点则返回 None。find_all(class_='classname')
: 查找具有特定 CSS 类名的所有标签。
-
Threading
threading
是 Python 标准库中用于线程编程的模块。线程是一种轻量级的执行单元,允许程序并发执行多个任务。threading
模块提供了创建、管理和同步线程的工具,可以用于在多任务环境中执行并行的操作- 常用方法:
threading.Thread(target, args, kwargs)
: 创建一个新线程。target
参数指定线程要执行的目标函数,args
和kwargs
用于传递给目标函数的参数。start()
: 启动线程,调用线程的run
方法。join(timeout=None)
: 等待线程终止。如果指定了timeout
参数,最多等待指定的秒数。
-
Requests
requests
是一个用于发送 HTTP 请求的 Python 库,它简化了与 Web 服务的交互。requests
库支持多种 HTTP 方法,包括 GET、POST、PUT、DELETE 等,并提供了便捷的接口来处理请求和响应。- 常用方法:
- response = requests.get('https://www.example.com')
- response = requests.post('https://www.example.com/post-endpoint', data={'key1': 'value1', 'key2': 'value2'})
- response = requests.get('https://www.example.com', headers={'User-Agent': 'my-app'} )
- response = requests.get('https://www.example.com', cookies={'user': 'my_user', 'token': 'my_token'})
-
Queue
Queue
是 Python 中的标准库queue
模块提供的队列类,用于在多线程编程中实现线程安全的队列操作。队列是一种常见的数据结构,具有先进先出(FIFO)的特性,即先放入队列的元素将先被取出。- 常用方法:
Queue(maxsize=0)
:
- 创建一个新的队列对象。
maxsize
参数可用于指定队列的最大大小,如果为0则表示队列大小无限制。
q.put(item, block=True, timeout=None)
:
- 将
item
放入队列。如果block
为 True(默认值),并且timeout
为 None(默认值),则在队列未满时立即返回;如果队列已满,则阻塞直到队列有空间。如果timeout
不为 None,则在超时之前阻塞。
q.get(block=True, timeout=None)
:
- 从队列中取出并返回一个元素。如果
block
为 True(默认值),并且timeout
为 None(默认值),则在队列非空时立即返回;如果队列为空,则阻塞直到队列有元素。如果timeout
不为 None,则在超时之前阻塞。
q.qsize()
:
- 返回队列中的元素数量。注意,由于多线程环境中,队列的大小可能在
qsize()
返回之后,但在实际操作之前发生改变,因此该方法仅提供一个近似值。
q.empty()
:
- 如果队列为空,返回 True;否则返回 False。
q.full()
:
- 如果队列已满,返回 True;否则返回 False。
q.put_nowait(item)
和q.get_nowait()
:
- 分别是
put()
和get()
的非阻塞版本。它们等效于put(item, block=False)
和get(block=False)
。
-
RE
re
是 Python 中的正则表达式模块,它提供了对正则表达式的支持,用于在字符串中进行模式匹配、搜索和替换等操作。正则表达式是一种强大的文本处理工具,允许你描述字符串的模式,并进行灵活的匹配和提取操作。常用方法:
re.match(pattern, string, flags=0)
:
- 尝试从字符串的开头匹配一个模式,如果匹配成功返回一个匹配对象,否则返回
None
。
re.search(pattern, string, flags=0)
:
- 在字符串中搜索模式,返回第一个匹配对象。如果没有匹配到,返回
None
。
re.findall(pattern, string, flags=0)
:
- 在字符串中查找所有匹配的子串,并以列表形式返回。每个匹配项是字符串的一部分。
re.finditer(pattern, string, flags=0)
:
- 返回一个迭代器,生成所有匹配的匹配对象。
re.sub(pattern, replacement, string, count=0, flags=0)
:
- 在字符串中替换匹配的模式为指定的替换字符串。可选的
count
参数指定替换的最大次数。
re.compile(pattern, flags=0)
:
- 编译正则表达式模式,返回一个正则表达式对象。通过编译可以提高匹配效率,尤其在多次使用同一模式时。
re.split(pattern, string, maxsplit=0, flags=0)
:
- 使用指定的模式分割字符串,返回分割后的列表。
案例介绍
对乌云网进行爬取,爬取的内容为(支付漏洞的搜索结果),搜索支付漏洞,将搜索出来的标题和链接爬取下来。https://wy.zone.ci/searchbug.php?q=%E6%94%AF%E4%BB%98%E6%BC%8F%E6%B4%9E&page=15
搜索支付漏洞,查询到231条记录,共16页,每条代码的规律是
<a href="bug_detail.php?wybug_id=xxx">xxx</a>
知道以上这些信息,就可以开始编写代码爬取数据了
代码实现
# 实战乌云
#引入解析和提取HTML/XML文档中的数据的库
from bs4 import BeautifulSoup
#引入线程模块
import threading
#引入请求响应HTTP获取网页信息模块
import requests
#引入消息队列模块
from queue import Queue
#引入正则表达式模块
import re
#创建一个队列,FIFO First in First Out : 先入先出
fifo = Queue()
#定义一个名为thread_body的函数/方法,并接收url,m两个参数
def thread_body(url, m):
#定义一个名为visited_links的集合,用于存储不重复元素的数据结构
visited_links = set()
#自定义HTTP请求体,以键值对方式定义,User-Agent等信息可以在控制台自行查看
header = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36"}
#用get方法请求url,将响应的数据赋给data
data = requests.get(url, headers=header)
#判断上面的请求是否成功,如果状态码为200则请求成功
if data.status_code == 200:
#创建实例soup,用data.text的方法获取网页的源码和数据,用BeautifulSoup解析提取
soup = BeautifulSoup(data.text, 'html.parser')
#re.compile为匹配的正则表达式,找到soup中所有a标签中属性href的值满足正则表达式的内容并赋值给data1
data1 = soup.find_all(name='a', attrs={'href': re.compile('^bug_detail.php?')})
#遍历data1中的数据
for j in data1:
#a标签中的的文字
link_text = j.text
#a标签的href属性值
link_href = j['href']
#刚才上面定义了一个visited_links集合,如果a标签的href属性值link_href不在集合中就把数据放到队列和集合中(防止数据重复)
if link_href not in visited_links:
#将数据放入队列中
fifo.put({link_text: link_href})
#将队列放入集合中
visited_links.add(link_href)
#如果网站访问不成功
else:
#输出请求第{m}页失败,m为函数接收的第二个参数
print(f"请求第{m}页失败")
#定义一个空列表用来放线程
threads = []
#一共有16页,循环16次
for m in range(1, 17):
#url就是上面要传入的参数,每次循环url都会改变
url = "https://wy.zone.ci/searchbug.php?q=支付漏洞&page=%s" % m
#每次循环都用threading.Thread方法启动一个线程,每个线程都去调用上面定义的thread_body函数,并把url和m传入函数
t = threading.Thread(target=thread_body, name="线程%s" % m, args=(url, m))
#启动线程
t.start()
#把线程放入刚才定义的放线程的空列表
threads.append(t)
#每启动一个线程后都输出一次线程启动
print("线程%s启动" % m)
#遍历上面的线程列表
for n in threads:
#用join方法实现:等每一个子线程结束了,再继续执行下面的主线程
t.join()
#到这儿线程都执行结束了,如果队列不为空就输出队列中的内容
while not fifo.empty():
#用get方法从队列取出数据
item = fifo.get()
print(item)
#扩展(可选),将数据存储到本地csv文件中,(思路参考,代码需要优化)
#导入csv模块
import csv
#定义一个列表放数据,data[{key1:value1},{key2:value2},{key3:valyue3}......]
data = []
#遍历获取队列数据,将数据放入列表中
for k in range(fifo.qsize()):
item = fifo.get()
data.append(item)
#打开文件夹(没有会自动创建),设置字符属性等等
with open("./spider4.csv",mode="a",encoding="utf-8",newline="") as f:
#以字典的方式将数据写入文件
writer=csv.DictWriter(f,fieldnames=["标题","网页"],extrasaction='ignore')
#写入头部
writer.writeheader()
#多行写入
writer.writerows(data)
结果展示
此次案例属于python爬虫初级案例,更多实用技巧有待学习,如有错误敬请指出改正!