?点击“博文视点Broadview”,获取更多书讯
你是否有这样的问题,学 Python 语言之后,能做什么?
Python的用途其实非常多,今天先来展示一个用Python采集漫画信息的实操案例。
01目标站点数据源分析
▊ 爬去目标
本次要爬取的网站是一个漫画网站,打开站点,呈现在我们面前的是 15660 部漫画信息,我们的目标是全部“拿下”。
为了降低学习难度,本文只介绍针对列表页进行抓取的过程,里面涉及的目标数据结构如下。
1. 漫画标题
2. 漫画评分
3. 漫画详情页链接
4. 作者
同时,在案例代码中需要同步保存漫画封面,并以封面文件名为漫画命名。
漫画详情页还存在大量的标签,因不涉及数据分析,故不再提取。
▊ 涉及的Python模块
爬取模块requests,数据提取模块re,多线程模块 threading。
▊ 重点学习内容
爬虫编写流程。
数据提取,重点掌握行内 CSS 提取。
CSV 格式文件存储。
变换IP 规避反爬,该目标网站有反爬机制。
▊ 列表页分析
通过点击测试,得到的页码变化逻辑如下。
https://vol.moe/l/all,all,all,sortpoint,all,all,BL/1.htm
https://vol.moe/l/all,all,all,sortpoint,all,all,BL/2.htm
https://vol.moe/l/all,all,all,sortpoint,all,all,BL/524.htm
页面数据是服务器直接静态返回的,加载到 HTML 页面中。
目标数据所在的 HTML DOM 结构如下所示。
在正式编写代码前,可以先有针对性地处理一下正则表达式部分。
▊ 匹配封面图
在编写该部分正则表达式时,出现了下图所示的“折行+括号”问题。
正则表达式如下,这里重点需要注意 \s 可匹配换行符。
<div[.\s]*style=”background:url\((.*?)\)▊ 匹配文章标题、作者、详情页地址
该部分内容所在的 DOM 行格式比较特殊,可以直接匹配出结果。下述正则表达式使用了分组命名的方法。
<a href=(?P<url>.*?)>(?P<title>.*?)</a> <br /> (?P<author>.*?) <br />对于动漫评分部分的匹配就非常简单了,直接编写如下正则表达式即可。
<p style=“.*?”><b>(.*?)</b></p>02整理出的需求
整理需求如下。
1. 开启 5 个线程对数据进行爬取。
2. 保存所有的网页源码。
3. 依次读取源码,提取元素到 CSV 文件中。
本案例将优先保存网页源码到本地,然后对本地 HTML 文件进行处理。
03编码时间
在编码测试的过程中,我们发现该网站存在反爬措施,会直接封杀爬虫程序的IP,导致程序还没有跑起来,就结束了。
再次测试,发现反爬技术使用重定向操作,即服务器发现是爬虫后,直接返回状态码 302,并重定向谷歌网站。
IP受限制的时间大概是 24 个小时,因此如果希望爬取到全部数据,需要通过不断切换 IP 和 UA ,将 HTML 静态文件保存到本地。
下述代码可以判断目标网站返回的状态码,如果为 302,则更换代理 IP,再次爬取。
import requestsimport reimport threadingimport timeimport random# 以下为UA列表,为节省篇幅只保留两项,完整版请联系作者获取USER_AGENTS = [ “Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)”, “Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)”,]# 循环获取 URLdef get_image(base_url, index): headers = { “User-Agent”: random.choice(USER_AGENTS) } print(f”正在抓取{index}“) try: res = requests.get(url=base_url, headers=headers, allow_redirects=False, timeout=10) print(res.status_code) # 当服务器返回 302状态码之后 while res.status_code == 302: # 可直接访问 http://118.24.52.95:5010/get/ 获得一个代理IP ip_json = requests.get(“http://118.24.52.95:5010/get/”, headers=headers).json() ip = ip_json[“proxy”] proxies = { “http”: ip, “https”: ip } print(proxies) # 使用代理IP继续爬取 res = requests.get(url=base_url, headers=headers, proxies=proxies, allow_redirects=False, timeout=10) time.sleep(5) print(res.status_code) else: html = res.text # 读取成功,保存为 html 文件 with open(f”html/{index}.html”, “w+”, encoding=“utf-8”) as f: f.write(html) semaphore.release() except Exception as e: print(e) print(“睡眠 10s,再去抓取”) time.sleep(10) get_image(base_url, index)if __name__ == __main__: num = 0 # 最多开启5个线程 semaphore = threading.BoundedSemaphore(5) lst_record_threads = [] for index in range(1, 525): semaphore.acquire() t = threading.Thread(target=get_image, args=( f”https://vol.moe/l/all,all,all,sortpoint,all,all,BL/{index}.htm”, index)) t.start() lst_record_threads.append(t) for rt in lst_record_threads: rt.join()只开启了 5 个线程,爬取过程如下所示。
经过 20 多分钟的等待,524 页数据全部以静态页形式保存在了本地 HTML 文件夹中。
当文件全部存储到本地之后,再进行数据提取就非常简单了。编写如下提取代码,使用 os 模块。
import osimport reimport requestsdef reade_html(): path = r”E:\pythonProject\test\html” # 读取文件 files = os.listdir(path) for file in files: # 拼接完整路径 file_path = os.path.join(path, file) with open(file_path, “r”, encoding=“utf-8”) as f: html = f.read() # 正则提取图片 img_pattern = re.compile(<div[.\s]*style=”background:url\((.*?)\)) # 正则提取标题 title_pattern = re.compile(“<a href=(?P<url>.*?)>(?P<title>.*?)</a> <br /> \[(?P<author>.*?)\] <br />”) # 正则提取得到 score_pattern = re.compile(<p style=”.*?”><b>(.*?)</b></p>) img_urls = img_pattern.findall(html) details = title_pattern.findall(html) scores = score_pattern.findall(html) # save(details, scores) for index, url in enumerate(img_urls): save_img(details[index][1], url)# 数据保存成 csv 文件def save(details, scores): for index, detail in enumerate(details): my_str = “%s,%s,%s,%s\n” % (detail[1].replace(“,”, “,”), detail[0], detail[2].replace(“,”, “,”), scores[index]) with open(“./comic.csv”, “a+”, encoding=“utf-8”) as f: f.write(my_str)# 图片按照动漫标题命名def save_img(title, url): print(f”正在抓取{title}—{url}“) headers = { “User-Agent”: “Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52” } try: res = requests.get(url, headers=headers, allow_redirects=False, timeout=10) data = res.content with open(f”imgs/{title}.jpg”, “wb+”) as f: f.write(data) except Exception as e: print(e)if __name__ == __main__:reade_html()代码提取到 csv 文件如下所示。
关于详情页,即动漫的更新信息,可以使用上述逻辑再次爬取。
04总结
完全学会本文中的操作,不仅需要用到 Python 的基础知识,还要掌握Python requests 模块、正则模块 re 、文件读写、多线程模块的相关内容。
想要快速学习这些内容,可以从阅读《滚雪球学Python》这本书开始。
滚雪球学 Python,书如其名,教授大家用类似滚雪球的思维学习 Python,第一遍浏览 Python 核心内容,第二遍补齐周边知识,第三遍夯实,第四遍拔高。每一遍滚雪球式的学习,都能丰富自己的知识。
限时五折优惠,快快扫码抢购吧!
发布:刘恩惠
审核:陈歆懿
如果喜欢本文欢迎 在看丨留言丨分享至朋友圈 三连< PAST · 往期回顾 >Python,30年上位之路! 点击阅读原文,查看本书详情!