Python商业数据挖掘实战——爬取网页并将其转为Markdown

news2024/11/18 0:12:42

前言

在这里插入图片描述
「作者主页」:雪碧有白泡泡
「个人网站」:雪碧的个人网站
请添加图片描述

ChatGPT体验地址

请添加图片描述

文章目录

  • 前言
  • 前言
  • 正则表达式
  • 进行转换
  • 送书活动

前言

在信息爆炸的时代,互联网上的海量文字信息如同无尽的沙滩。然而,其中真正有价值的信息往往埋在各种网页中,需要经过筛选和整理才能被有效利用。幸运的是,Python这个强大的编程语言可以帮助我们完成这项任务。

本文将介绍如何使用Python将网页文字转换为Markdown格式,这将使得我们能够更加方便地阅读和处理网页内容。无论是将文章保存为本地文件还是转化为其他格式,Markdown都能够提供清晰简洁的排版和格式,让我们更加专注于内容本身。

正则表达式

我们将页面进行Maekdown的转换为了保证准确度,我们可以使用正则表达式去修改,如下

import re

__all__ = ['Tomd', 'convert']

MARKDOWN = {
    'h1': ('\n# ', '\n'),
    'h2': ('\n## ', '\n'),
    'h3': ('\n### ', '\n'),
    'h4': ('\n#### ', '\n'),
    'h5': ('\n##### ', '\n'),
    'h6': ('\n###### ', '\n'),
    'code': ('`', '`'),
    'ul': ('', ''),
    'ol': ('', ''),
    'li': ('- ', ''),
    'blockquote': ('\n> ', '\n'),
    'em': ('**', '**'),
    'strong': ('**', '**'),
    'block_code': ('\n```\n', '\n```\n'),
    'span': ('', ''),
    'p': ('\n', '\n'),
    'p_with_out_class': ('\n', '\n'),
    'inline_p': ('', ''),
    'inline_p_with_out_class': ('', ''),
    'b': ('**', '**'),
    'i': ('*', '*'),
    'del': ('~~', '~~'),
    'hr': ('\n---', '\n\n'),
    'thead': ('\n', '|------\n'),
    'tbody': ('\n', '\n'),
    'td': ('|', ''),
    'th': ('|', ''),
    'tr': ('', '\n')
}

BlOCK_ELEMENTS = {
    'h1': '<h1.*?>(.*?)</h1>',
    'h2': '<h2.*?>(.*?)</h2>',
    'h3': '<h3.*?>(.*?)</h3>',
    'h4': '<h4.*?>(.*?)</h4>',
    'h5': '<h5.*?>(.*?)</h5>',
    'h6': '<h6.*?>(.*?)</h6>',
    'hr': '<hr/>',
    'blockquote': '<blockquote.*?>(.*?)</blockquote>',
    'ul': '<ul.*?>(.*?)</ul>',
    'ol': '<ol.*?>(.*?)</ol>',
    'block_code': '<pre.*?><code.*?>(.*?)</code></pre>',
    'p': '<p\s.*?>(.*?)</p>',
    'p_with_out_class': '<p>(.*?)</p>',
    'thead': '<thead.*?>(.*?)</thead>',
    'tr': '<tr>(.*?)</tr>'
}

INLINE_ELEMENTS = {
    'td': '<td>(.*?)</td>',
    'tr': '<tr>(.*?)</tr>',
    'th': '<th>(.*?)</th>',
    'b': '<b>(.*?)</b>',
    'i': '<i>(.*?)</i>',
    'del': '<del>(.*?)</del>',
    'inline_p': '<p\s.*?>(.*?)</p>',
    'inline_p_with_out_class': '<p>(.*?)</p>',
    'code': '<code.*?>(.*?)</code>',
    'span': '<span.*?>(.*?)</span>',
    'ul': '<ul.*?>(.*?)</ul>',
    'ol': '<ol.*?>(.*?)</ol>',
    'li': '<li.*?>(.*?)</li>',
    'img': '<img.*?src="(.*?)".*?>(.*?)</img>',
    'a': '<a.*?href="(.*?)".*?>(.*?)</a>',
    'em': '<em.*?>(.*?)</em>',
    'strong': '<strong.*?>(.*?)</strong>'
}

DELETE_ELEMENTS = ['<span.*?>', '</span>', '<div.*?>', '</div>']


class Element:
    def __init__(self, start_pos, end_pos, content, tag, is_block=False):
        self.start_pos = start_pos
        self.end_pos = end_pos
        self.content = content
        self._elements = []
        self.is_block = is_block
        self.tag = tag
        self._result = None

        if self.is_block:
            self.parse_inline()

    def __str__(self):
        wrapper = MARKDOWN.get(self.tag)
        self._result = '{}{}{}'.format(wrapper[0], self.content, wrapper[1])
        return self._result

    def parse_inline(self):
        for tag, pattern in INLINE_ELEMENTS.items():

            if tag == 'a':
                self.content = re.sub(pattern, '[\g<2>](\g<1>)', self.content)
            elif tag == 'img':
                self.content = re.sub(pattern, '![\g<2>](\g<1>)', self.content)
            elif self.tag == 'ul' and tag == 'li':
                self.content = re.sub(pattern, '- \g<1>', self.content)
            elif self.tag == 'ol' and tag == 'li':
                self.content = re.sub(pattern, '1. \g<1>', self.content)
            elif self.tag == 'thead' and tag == 'tr':
                self.content = re.sub(pattern, '\g<1>\n', self.content.replace('\n', ''))
            elif self.tag == 'tr' and tag == 'th':
                self.content = re.sub(pattern, '|\g<1>', self.content.replace('\n', ''))
            elif self.tag == 'tr' and tag == 'td':
                self.content = re.sub(pattern, '|\g<1>', self.content.replace('\n', ''))
            else:
                wrapper = MARKDOWN.get(tag)
                self.content = re.sub(pattern, '{}\g<1>{}'.format(wrapper[0], wrapper[1]), self.content)


class Tomd:
    def __init__(self, html='', options=None):
        self.html = html
        self.options = options
        self._markdown = ''

    def convert(self, html, options=None):
        elements = []
        for tag, pattern in BlOCK_ELEMENTS.items():
            for m in re.finditer(pattern, html, re.I | re.S | re.M):
                element = Element(start_pos=m.start(),
                                  end_pos=m.end(),
                                  content=''.join(m.groups()),
                                  tag=tag,
                                  is_block=True)
                can_append = True
                for e in elements:
                    if e.start_pos < m.start() and e.end_pos > m.end():
                        can_append = False
                    elif e.start_pos > m.start() and e.end_pos < m.end():
                        elements.remove(e)
                if can_append:
                    elements.append(element)

        elements.sort(key=lambda element: element.start_pos)
        self._markdown = ''.join([str(e) for e in elements])

        for index, element in enumerate(DELETE_ELEMENTS):
            self._markdown = re.sub(element, '', self._markdown)
        return self._markdown

    @property
    def markdown(self):
        self.convert(self.html, self.options)
        return self._markdown


_inst = Tomd()
convert = _inst.convert

这段代码是一个用于将HTML转换为Markdown的工具类。它使用了正则表达式来解析HTML标签,并根据预定义的转换规则将其转换为对应的Markdown格式。

代码中定义了一个Element类,用于表示HTML中的各个元素。Element类包含了标签的起始位置、结束位置、内容、标签类型等信息。它还提供了一个parse_inline方法,用于解析内联元素,并将其转换为Markdown格式。

Tomd类是主要的转换类,它接受HTML字符串并提供了convert方法来执行转换操作。convert方法遍历预定义的HTML标签模式,并使用正则表达式匹配HTML字符串中对应的部分。然后创建相应的Element对象并进行转换操作。最后,将转换后的Markdown字符串返回。

在模块顶部,MARKDOWN字典定义了各个HTML标签对应的Markdown格式。BlOCK_ELEMENTSINLINE_ELEMENTS字典定义了正则表达式模式,用于匹配HTML字符串中的块级元素和内联元素。DELETE_ELEMENTS列表定义了需要删除的HTML元素。

那么既然有了转markdown的工具,我们就可以对网页进行转换

进行转换

首先,result_file函数用于创建一个保存结果文件的路径。它接受文件夹的用户名、文件名和文件夹名作为参数,并在指定的文件夹路径下创建一个新的文件,并返回该文件的路径。

get_headers函数用于从一个文本文件中读取Cookie,并将它们保存为字典形式。它接受包含Cookie的文本文件路径作为参数。

delete_ele函数用于删除BeautifulSoup对象中指定的标签。它接受一个BeautifulSoup对象和待删除的标签列表作为参数,并通过使用该对象的select方法来选择要删除的标签,然后使用decompose方法进行删除。

delete_ele_attr函数用于删除BeautifulSoup对象中指定标签的指定属性。它接受一个BeautifulSoup对象和待删除的属性列表作为参数,并使用find_all方法来选取所有标签,然后使用Python的del语句删除指定的属性。

delete_blank_ele函数用于删除BeautifulSoup对象中的空白标签。它接受一个BeautifulSoup对象和一个例外列表,对于不在例外列表中且内容为空的标签,使用decompose方法进行删除。

TaskQueue类是一个简单的任务队列,用于存储已访问的和未访问的URL。它提供了一系列方法来操作这些列表。

def result_file(folder_username, file_name, folder_name):
	folder = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", folder_name, folder_username)
	if not os.path.exists(folder):
		try:
			os.makedirs(folder)
		except Exception:
			pass
		path = os.path.join(folder, file_name)
		file = open(path,"w")
		file.close()
	else:
		path = os.path.join(folder, file_name)
	return path


def get_headers(cookie_path:str):
	cookies = {}
	with open(cookie_path, "r", encoding="utf-8") as f:
		cookie_list = f.readlines()
	for line in cookie_list:
		cookie = line.split(":")
		cookies[cookie[0]] = str(cookie[1]).strip()
	return cookies


def delete_ele(soup:BeautifulSoup, tags:list):
	for ele in tags:
		for useless_tag in soup.select(ele):
			useless_tag.decompose()


def delete_ele_attr(soup:BeautifulSoup, attrs:list):
	for attr in attrs:
		for useless_attr in soup.find_all():
			del useless_attr[attr]


def delete_blank_ele(soup:BeautifulSoup, eles_except:list):
	for useless_attr in soup.find_all():
		try:
			if useless_attr.name not in eles_except and useless_attr.text == "":
				useless_attr.decompose()
		except Exception:
			pass


class TaskQueue(object):
	def __init__(self):
		self.VisitedList = []
		self.UnVisitedList = []
	
	def getVisitedList(self):
		return self.VisitedList

	def getUnVisitedList(self):
		return self.UnVisitedList
	
	def InsertVisitedList(self, url):
		if url not in self.VisitedList:
			self.VisitedList.append(url)
	
	def InsertUnVisitedList(self, url):
		if url not in self.UnVisitedList:
			self.UnVisitedList.append(url)
	
	def RemoveVisitedList(self, url):
		self.VisitedList.remove(url)

	def PopUnVisitedList(self,index=0):
		url = []
		if index and self.UnVisitedList:
			url = self.UnVisitedList[index]
			del self.UnVisitedList[:index]
		elif self.UnVisitedList:
			url = self.UnVisitedList.pop()
		return url
	
	def getUnVisitedListLength(self):
		return len(self.UnVisitedList)


class CSDN(object):
	def __init__(self, username, folder_name, cookie_path):
		# self.headers = {
		# 	"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36"
		# }
		self.headers = get_headers(cookie_path)
		self.s = requests.Session()
		self.username = username
		self.TaskQueue = TaskQueue()
		self.folder_name = folder_name
		self.url_num = 1

	def start(self):
		num = 0
		articles = [None]
		while len(articles) > 0:
			num += 1
			url = u'https://blog.csdn.net/' + self.username + '/article/list/' + str(num)
			response = self.s.get(url=url, headers=self.headers)
			html = response.text
			soup = BeautifulSoup(html, "html.parser")
			articles = soup.find_all('div', attrs={"class":"article-item-box csdn-tracking-statistics"})
			for article in articles:
				article_title = article.a.text.strip().replace('        ',':')
				article_href = article.a['href']
				with ensure_memory(sys.getsizeof(self.TaskQueue.UnVisitedList)):
					self.TaskQueue.InsertUnVisitedList([article_title, article_href])
	
	def get_md(self, url):
		response = self.s.get(url=url, headers=self.headers)
		html = response.text
		soup = BeautifulSoup(html, 'lxml')
		content = soup.select_one("#content_views")
		# 删除注释
		for useless_tag in content(text=lambda text: isinstance(text, Comment)):
			useless_tag.extract()
		# 删除无用标签
		tags = ["svg", "ul", ".hljs-button.signin"]
		delete_ele(content, tags)
		# 删除标签属性
		attrs = ["class", "name", "id", "onclick", "style", "data-token", "rel"]
		delete_ele_attr(content,attrs)
		# 删除空白标签
		eles_except = ["img", "br", "hr"]
		delete_blank_ele(content, eles_except)
		# 转换为markdown
		md = Tomd(str(content)).markdown
		return md


	def write_readme(self):
		print("+"*100)
		print("[++] 开始爬取 {} 的博文 ......".format(self.username))
		print("+"*100)
		reademe_path = result_file(self.username,file_name="README.md",folder_name=self.folder_name)
		with open(reademe_path,'w', encoding='utf-8') as reademe_file:
			readme_head = "# " + self.username + " 的博文\n"
			reademe_file.write(readme_head)
			for [article_title,article_href] in self.TaskQueue.UnVisitedList[::-1]:
					text = str(self.url_num) + '. [' + article_title + ']('+ article_href +')\n'
					reademe_file.write(text)
					self.url_num += 1
		self.url_num = 1
	
	def get_all_articles(self):
		try:
			while True:
				[article_title,article_href] = self.TaskQueue.PopUnVisitedList()
				try:
					file_name = re.sub(r'[\/::*?"<>|]','-', article_title) + ".md"
					artical_path = result_file(folder_username=self.username, file_name=file_name, folder_name=self.folder_name)
					md_head = "# " + article_title + "\n"
					md = md_head + self.get_md(article_href)
					print("[++++] 正在处理URL:{}".format(article_href))
					with open(artical_path, "w", encoding="utf-8") as artical_file:
						artical_file.write(md)
				except Exception:
					print("[----] 处理URL异常:{}".format(article_href))
				self.url_num += 1
		except Exception:
			pass

	def muti_spider(self, thread_num):
		while self.TaskQueue.getUnVisitedListLength() > 0:
			thread_list = []
			for i in range(thread_num):
				th = threading.Thread(target=self.get_all_articles)
				thread_list.append(th)
			for th in thread_list:
				th.start()


lock = threading.Lock()
total_mem= 1024 * 1024 * 500 #500MB spare memory
@contextlib.contextmanager
def ensure_memory(size):
    global total_mem
    while 1:
        with lock:
            if total_mem > size:
                total_mem-= size
                break
        time.sleep(5)
    yield 
    with lock:
        total_mem += size


def spider_user(username: str, cookie_path:str, thread_num: int = 10, folder_name: str = "articles"):
	if not os.path.exists(folder_name):
		os.makedirs(folder_name)
	csdn = CSDN(username, folder_name, cookie_path)
	csdn.start()
	th1 = threading.Thread(target=csdn.write_readme)
	th1.start()
	th2 = threading.Thread(target=csdn.muti_spider, args=(thread_num,))
	th2.start()


def spider(usernames: list, cookie_path:str, thread_num: int = 10, folder_name: str = "articles"):
	for username in usernames:
		try:
			user_thread = threading.Thread(target=spider_user,args=(username, cookie_path, thread_num, folder_name))
			user_thread.start()
			print("[++] 开启爬取 {} 博文进程成功 ......".format(username))
		except Exception:
			print("[--] 开启爬取 {} 博文进程出现异常 ......".format(username))

我们可以自定义一个测试类运行一下,在本地文件位置会生成一个文件夹,并将markdown文件输出出来
在这里插入图片描述
需要完整源码的小伙伴可以加文末底部微信私信获取哦,公众号内有联系方式

送书活动

  • 🎁本次送书1~3本【取决于阅读量,阅读量越多,送的越多】👈
  • ⌛️活动时间:截止到2023-12月27号
  • ✳️参与方式:关注博主+三连(点赞、收藏、评论)

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1377226.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【源码阅读】事件订阅包v2

1、Feed Feed 实现一对多订阅&#xff0c;其中事件的载体是通道。发送到 Feed 的值会同时传送到所有订阅的通道。 与Typemux的对比 链接: link TypeMux是一个同步的事件框架&#xff0c;当有一个被订阅的事件发生的时候&#xff0c;会遍历该事件对应的订阅者通道&#xff0c;…

ZigBee快速入门——外部中断(Key)

外部中断 :::tips 理解三道锁&#xff1a;EA——IENx——PxIEN EA-总开关 IENx-中断使能功能配置&#xff0c;可以配置程总线 IO中断&#xff08;P0、P1、P2&#xff09;&#xff0c;也可以配置程定时器等中断 PxIEN-总线中具体某一位的中断允许&#xff0c;如刚刚已经配置了 I…

vue3项目部署到服务器,刚打开没事,一刷新页面就404

vue3项目部署到服务器&#xff0c;刚打开没事&#xff0c;一刷新页面就404 vue3项目&#xff0c;在本地调试时各方面都没毛病&#xff0c;刷新也没毛病&#xff0c;但是&#xff0c;扔到服务器上&#xff0c;第一次打开是正常的&#xff0c;再刷新下就404了&#xff0c;不知道什…

软件测评中心▏性能测试之压力测试、负载测试的区别和联系简析

在如今的信息时代&#xff0c;软件已经成为人们日常工作和生活不可或缺的一部分。然而&#xff0c;随着软件的发展和应用范围的不断扩大&#xff0c;软件性能的优劣也成为了影响用户使用体验的重要因素。 软件性能测试即对软件在不同条件下的性能进行评估和验证的过程。通过模…

《MCtalk·CEO对话》正式上线!首期对话高成资本

2015 年 10 月&#xff0c;网易智企发布第一款产品&#xff0c;正式踏上了 ToB 商业化之路。从那以后&#xff0c;我们每年举办不同主题的科技峰会&#xff0c;分享最新的行业体感和洞察&#xff1b;访谈各界企业领导者&#xff0c;记录他们的创新与创业经历&#xff1b;走过大…

黑帽SEO简介

什么是黑帽 SEO&#xff1f; 黑帽SEO是一种违反搜索引擎指南的做法&#xff0c;用于使网站在搜索结果中排名更高。这些不道德的策略并不能解决搜索者的问题&#xff0c;并且通常以搜索引擎的惩罚而告终。黑帽技术包括关键字填充、伪装和使用专用链接网络。 出现在搜索结果中对…

确定性网络技术怎样实现网络的可靠性?

确定性网络技术通过采用特定的协议、机制和策略&#xff0c;有助于提高网络的可靠性。本文通过一些关键的方面&#xff0c;来说明确定性网络技术如何实现这一目标。 时钟同步机制 时钟同步机制是确定性网络中的核心角色。为了实现高度可靠的通信&#xff0c;需要采用先进的时钟…

如何进行有竞争力的SEO审计以超越行业竞争对手

许多营销人员都有兴趣密切关注竞争对手的搜索引擎优化 &#xff08;SEO&#xff09;。这是有道理的——无论你是刚开始做SEO&#xff0c;还是已经做了一段时间&#xff0c;你都希望对搜索引擎结果页面&#xff08;SERP&#xff09;的竞争格局有一个清晰的认识&#xff0c;这样你…

构建基于RHEL9系列(CentOS9,AlmaLinux9,RockyLinux9等)的MySQL8.0.32的RPM包

本文适用&#xff1a;rhel9系列&#xff0c;或同类系统(CentOS9,AlmaLinux9,RockyLinux9等) 文档形成时期&#xff1a;2023年 因系统版本不同&#xff0c;构建部署应略有差异&#xff0c;但本文未做细分&#xff0c;对稍有经验者应不存在明显障碍。 因软件世界之复杂和个人能力…

2024年软件测试面试八股文【含答案】

Part1 1、你的测试职业发展是什么&#xff1f;【文末有面试文档免费领取】 测试经验越多&#xff0c;测试能力越高。所以我的职业发展是需要时间积累的&#xff0c;一步步向着高级测试工程师奔去。而且我也有初步的职业规划&#xff0c;前3年积累测试经验&#xff0c;按如何做…

SpringBoot3.X源码分析(启动流程)

SpringBootApplication(scanBasePackages {"com.javaedge.base"} ) public class BaseApplication {public BaseApplication() {}public static void main(String[] args) {SpringApplication.run(BaseApplication.class, args);} } 1 启动入口 静态辅助类&#x…

博弈类问题

巴什博弈(Bash Game) String bashGame2(int n, int m) {return n % (m 1) ! 0 ? "先手" : "后手";} #include<iostream> #include<string> using namespace std;string compute(int n) {return n % 6 ! 0 ? "October wins!" : &q…

iOS开发进阶(六):Xcode14 使用信号量造成线程优先级反转问题修复

文章目录 一、前言二、关于线程优先级反转三、优先级反转会造成什么后果四、怎么避免线程优先级反转五、使用信号量可能会造成线程优先级反转&#xff0c;且无法避免六、延伸阅读&#xff1a;iOS | Xcode中快速打开终端6.1 .sh绑定6.2 执行 pod install 脚本 七、延伸阅读&…

adrv9009使用记录

这里写自定义目录标题 1.首先下载cygwin&#xff0c;CSDN可以直接搜索&#xff0c;按照对应的安装就可以&#xff0c;最后记得加一个make安装包&#xff0c;不然在make时候会导致指令不存在 2.下载完成之后&#xff0c;去adi-github官网找到对应版本的adrv9009工程 https://git…

为什么要进行漏洞扫描工作

随着互联网的普及和信息技术的飞速发展&#xff0c;网络安全问题愈发引人关注。其中&#xff0c;漏洞扫描作为保障网络安全的重要手段&#xff0c;受到了广泛的关注和应用。本文将详细介绍漏洞扫描的概念、效果、使用场景等&#xff0c;以期为读者提供有关漏洞扫描的全面了解。…

PPT自动化处理

python-pptx模块 可以创建、修改PPT(.pptx)文件非Python标准模块&#xff0c;需要单独安装 在线安装方式 pip install python-pptx 读取slide幻灯片 .slides 获取shape形状 slide.shapes 判断一个shape中是否存在文字 shape.has_text_frame 获取文字框 shape.text_f…

查看磁盘里的大文件

查看磁盘里的大文件 在PowerShell中 命令1&#xff1a; gci -r| sort -descending -property length | select -first 10 name, length 命令2&#xff1a; Get-ChildItem -Path C:\ -Recurse | Where-Object { $_.Length -gt 1GB } | Sort-Object -Property Length -Descendin…

微服务自动化docker-compose

一、docker-compose介绍 Docker Compose是一个用来定义和运行多个复杂应用的Docker编排工具。例如&#xff0c;一个使用Docker容器的微服务项目&#xff0c;通常由多个容器应用组成。那么部署时如何快速启动各个微服务呢&#xff0c;一个个手动启动&#xff1f;假如有上百个微服…

Docker进阶数据卷目录挂载及在线部署

前言 为了很好的实现数据保存和数据共享&#xff0c; Docker 提出了 Volume 这个概念&#xff0c;简单的说就是绕过默认的联合 文件系统&#xff0c;而以正常的文件或者目录的形式存在于宿主机上。又被称作数据卷 一. 数据卷介绍 Docker 中的数据卷&#xff08;Volume&#x…

这是一款户外可充电多功能LED地摊灯 手电筒方案

1,信息来源&#xff1a;深圳市世微半导体有限公司 Augus 2,产品的特性有&#xff1a; 全集成单芯片控制 5 照明循环模式可选 0.5A/1A 固定充电电流可选 内置 MOS 1.8A 驱动电流 可外置 MOS 驱动更大电流 充电指示/低电提示/短路提示 3A 手电筒过流保护? 预设 4.22V 电…