前面是踩坑和总结,完整代码在最后
目标网站为:
主要内容
- 分页爬取
- URL拼接
- CSV数据存储
- CSV读取
- 图片下载
- 常见编码错误处理
一、分页爬取的核心思想
直接写只能获取首页数据:
python
url = "https://books.toscrape.com/"
res = requests.get(url)
但网站实际上有50页数据。
分页爬取的本质:
text
请求当前页
↓
解析数据
↓
找到下一页链接
↓
拼接URL
↓
继续请求
因此最终采用:
python
while url:
# 请求页面
# 解析数据
# 获取下一页
# 更新url
二、不要手动拼接URL
这是我最开始写法:
python
next_url = url + next_href
例如:
python
base_url = "https://books.toscrape.com/"
next_href = "page-3.html"
得到:
python
https://books.toscrape.com/page-3.html
这是错误的。
因为当前页面实际上位于:
python
https://books.toscrape.com/catalogue/page-2.html
正确地址应该是:
python
https://books.toscrape.com/catalogue/page-3.html
解决方案:
python
from urllib.parse import urljoin
next_url = urljoin(url, next_href)
以后遇到:
python
../
./
/path
等相对路径时都能自动处理。
三、next按钮不存在时的错误
最开始写法:
python
next_href = soup.find(
"li",
class_="next"
).find("a")["href"]
当最后一页没有next按钮时:
python
soup.find("li", class_="next")
返回:
python
None
程序报错:
python
AttributeError:
'NoneType' object has no attribute 'find'
正确写法:
python
next_li = soup.find(
"li",
class_="next"
)
if next_li is None:
break
next_href = next_li.find("a")["href"]
四、CSV写入位置问题
错误写法:
python
while url:
with open(
"books.csv",
"w"
) as f:
writer.writerow(...)
问题:
每次循环都会重新创建文件。
结果:
text
第一页数据被覆盖
第二页数据被覆盖
第三页数据被覆盖
...
最后只剩最后一页。
正确写法:
python
with open(
"books.csv",
"w",
newline="",
encoding="utf-8-sig"
) as f:
writer = csv.writer(f)
writer.writerow(
["书名","价格","库存","封面"]
)
while url:
writer.writerow(...)
文件只打开一次。
五、CSV编码问题
保存时:
python
with open(
"books.csv",
"w",
encoding="utf-8-sig"
)
从csv文件读取内容不要忘记指定编码:
python
with open(
"books.csv",
"r"
)
导致:
text
UnicodeDecodeError
原因:
Windows默认使用GBK读取。
而文件实际编码:
text
utf-8-sig
解决:
python
with open(
"books.csv",
"r",
encoding="utf-8-sig"
)
读写编码必须一致。
六、enumerate的使用
python
for i, item in enumerate(
data,
start=1
):
pass
优点:
- 代码更简洁
- 自动计数
- 可指定起始值
例如:
python
for i, item in enumerate(
reader,
start=1
):
得到:
text
1
2
3
...
七、下载图片
读取CSV中的图片地址:
python
reader = csv.DictReader(f)
for row in reader:
img_url = row["封面"]
下载:
python
res = requests.get(
img_url,
headers=headers,
proxies=proxy,
timeout=10
)
保存:
python
with open(
filename,
"wb"
) as file:
file.write(res.content)
注意:
python
wb
表示二进制写入。
图片、视频等文件都要使用:
python
wb
八、限制下载前10张图片
实现:
python
for i, row in enumerate(
reader,
start=1
):
if i > 10:
break
只下载:
text
image_1.png
image_2.png
...
image_10.png
用于测试非常方便。
九、本次项目最终结构
text
请求首页
↓
解析书籍数据
↓
写入CSV
↓
寻找下一页
↓
翻页
↓
保存全部数据
↓
读取CSV
↓
下载图片
已经完成:
✅ Requests基础使用
✅ BeautifulSoup解析
✅ 分页爬取
✅ URL拼接
✅ CSV存储
✅ CSV读取
✅ 图片下载
十、完整代码
python
import requests
from urllib.parse import urljoin
from bs4 import BeautifulSoup
import csv
import os
url = "https://books.toscrape.com/"
#代理,端口改成自己的
proxy = {
"http":"http://127.0.0.1:7892",
"https":"http://127.0.0.1:7892"
}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive"
}
def get_books():
global url
num = 0
with open("book_1.csv","w",newline="",encoding="utf-8-sig") as f:
writer = csv.writer(f)
writer.writerow(["书名","价格","库存","封面"])
while url:
num+=1
res = requests.get(url,proxies=proxy,headers=headers,timeout=10)
soup = BeautifulSoup(res.text,"html.parser")
#解析书籍
articles = soup.find_all("article",class_="product_pod")
for article in articles:
title = article.find("h3").find("a")["title"]
price = article.find("p",class_="price_color").text.strip()
instock = article.find("p",class_="instock availability").text.strip()
img_url = urljoin(url,article.find("img")["src"])
writer.writerow([title,price,instock,img_url])
print(f"当前第{num}页数据写入完成")
#找next,没有直接退出
next_li = soup.find("li",class_="next")
if next_li is None:
print("爬取完成")
break
next_url = next_li.find("a")["href"]
url = urljoin(url,next_url)
#保存图片
def save_image():
makedir = "down"
os.makedirs(makedir,exist_ok=True)
with open("book_1.csv","r",encoding="utf-8-sig") as f:
reader = csv.DictReader(f)
for i,read in enumerate(reader,start=1):
img_url = read["封面"]
res = requests.get(img_url,headers=headers,proxies=proxy,timeout=10)
if i>=10 :
print("保存成功")
break
filename = os.path.join(makedir,f"image_{i}.png")
with open(filename,"wb") as file:
file.write(res.content)
if __name__ == "__main__":
get_books()
save_image()