爬虫一般指从网络资源的抓取,通过Python语言的脚本特性,配置字符的处理非常灵活,Python有丰富的网络抓取模块,因而两者经常联系在一起Python就被叫作爬虫。爬虫可以抓取某个网站或者某个应用的内容提取有用的价值信息。有时还可以模拟用户在浏
览器或app应用上的操作行为,从而实现程序自动化。
1、爬虫架构
爬虫架构通常由5个部分组成,分别是调度器、URL管理器、网页下载器、网页解析器、应用程序。
- 调度器:相当电脑的CPU,主要负责调度URL管理器、下载器、解析器之间的协调工作。
- URL管理器:包括待爬取的URL地址和已爬取的URL地址,防止重复抓取URL和循环抓取URL。实现URL管理器通常有三种方式,通过内存、数据库、缓存方式实现。
- 网页下载器:通过传入一个URL地址来下载网页,将网页转换成一个字符串,网页下载器有urllib2(Python官方基础模块)包括需要登录、代理、和cookie,requests(第三方包)。
- 网页解析器:用于某个网页字符串进行解析,可以按照我们的要求来提取出有用的信息,也可以根据DOM树的解析方式来解析。常用的解析器有html.parser(python自带的)、beautifulsoup(也可以使用python自带的html.parser进行解析,也可以使用lxml进行解析,相对于其他几种来说要强大一些)、lxml(可以解析 xml 和 HTML),通过html.parser 和 beautifulsoup 以及 lxml 都是以DOM 树的方式进行解析。
- 应用程序:用于从网页中提取的有用数据组成的一个应用。
2、爬虫实现
2.1、Url管理器(基于内存)
python
class UrlManager():
"""
url 管理器,用来装载网址所有地址
"""
def __init__(self):
# 新url 集合
self.new_urls = set()
# 旧url 集合
self.old_urls = set()
def add_new_url(self, url):
"""
添加新的url到集合
:param url: url
:return:
"""
if url is None or len(url) == 0:
return
if url in self.new_urls or url in self.old_urls:
return
self.new_urls.add(url)
def add_new_urls(self, urls):
"""
批量添加urls
:param urls: url
:return:
"""
if urls is None or len(urls) == 0:
return
for url in urls:
self.add_new_url(url)
def get_url(self):
"""
获取url: 从new_urls集合获取url,放入到old_urls
:return:
"""
if self.has_new_url():
url = self.new_urls.pop()
self.old_urls.add(url)
return url
else:
return None
def has_new_url(self):
"""
判断是否有新的url
:return:
"""
return len(self.new_urls) > 0
if __name__ == '__main__':
url_manager = UrlManager()
url_manager.add_new_url('url1')
url_manager.add_new_urls(['url1','url2'])
print(url_manager.new_urls, url_manager.old_urls)
print("#" * 30)
new_url = url_manager.get_url()
print(url_manager.new_urls, url_manager.old_urls)
print("#" * 30)
new_url = url_manager.get_url()
print(url_manager.new_urls, url_manager.old_urls)
print("#" * 30)
print(url_manager.has_new_url())
2.2 网页解析
python
import re
from utils import url_manager
import requests
from bs4 import BeautifulSoup
def download_all_urls(root_url):
"""
爬取根网址所有页面的url
:param root_url: 根网址地址
:return:
"""
urls = url_manager.UrlManager()
urls.add_new_url(root_url)
fout = open("craw_all_pages.txt", "w", encoding="utf-8")
while urls.has_new_url():
curr_url = urls.get_url()
r = requests.get(curr_url, timeout=5)
r.encoding = "utf-8"
if r.status_code != 200:
print("error, return status_code is not 200", curr_url)
continue
soup = BeautifulSoup(r.text, "html.parser", from_encoding="utf-8")
title = soup.title.string
fout.write("%s\t%s\n" % (curr_url, title))
fout.flush()
print("success: %s, %s, %d" % (curr_url, title, len(urls.old_urls)))
links = soup.find_all("a")
for link in links:
href = link.get("href")
if href is None:
continue
#模式匹配
pattern = r'^https://www.runoob.com/python/\s+.html$'
if re.match(pattern, href):
urls.add_new_url(href)
fout.close()
if __name__ == '__main__':
#定义根网址url
root_url = "https://www.runoob.com/python/python-tutorial.html"
download_all_urls(root_url)
3、经典案例
例如:读取某网站Top250电影。
python
import pprint
import json
import requests
from bs4 import BeautifulSoup
import pandas as pd
"""
# requests 请求网站地址
# beautifulsoup4 解析网址的elements(div,class,<a>,<img> id)等
# pandas 将数据写入到excel
# 其他额外安装包openpyxl
"""
# 构造分页数字列表(步长/pageSize/step=25)
page_indexs = range(0, 250, 25)
list(page_indexs)
def download_all_htmls(root_url, headers):
"""
下载所有页面的html内容,用于后续分析
:return:
"""
htmls = []
for idx in page_indexs:
url = "%s?start=%d&filter=" % (root_url, idx)
# url = f"https://movie.douban.com/top250?start={idx}&filter="
print("craw html:", url)
r = requests.get(url, timeout=5, headers=headers)
if r.status_code != 200:
raise Exception("error")
htmls.append(r.text)
return htmls
def parse_single_html(html_doc):
soup = BeautifulSoup(html_doc, "html.parser")
article_items = (
soup.find("div", class_="article")
.find("ol", class_="grid_view")
.find_all("div", class_="item")
)
datas = []
for item in article_items:
rank = item.find("div", class_="pic").find("em").get_text()
info = item.find("div", class_="info")
title = info.find("div", class_="hd").find("span", class_="title").get_text()
stars = (
info.find("div", class_="bd")
.find("div", class_="star")
.find_all("span")
)
rating_star = stars[0]["class"][0]
rating_num = stars[1].get_text()
comments = stars[3].get_text()
datas.append({
"rank": rank,
"title": title,
"rating_star": rating_star.replace("rating", "").replace("-t", ""),
"rating_num": rating_num,
"comments": comments.replace("人评价", "")
})
return datas
def save_to_excel(all_datas):
"""
保存解析后数据到excel
:param all_datas: 传人数据
:return:
"""
# print table
df = pd.DataFrame(all_datas)
print(df)
df.to_excel("豆瓣电影TOP250.xlsx")
if __name__ == '__main__':
# 模拟用户行为
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36"
}
# 定义根网址url
root_url = "https://movie.douban.com/top250"
htmls = download_all_htmls(root_url, headers)
# beatiful print text
pprint.pprint(parse_single_html(htmls[0]))
# extend迭代添加到list,保存到excel
all_datas = []
for html in htmls:
all_datas.extend(parse_single_html(html))
print(len(all_datas))
save_to_excel(all_datas)