作为一名刚刚入门python和爬虫的小白,最近在学习github上面的爬虫入门项目(https://github.com/Dong-gxian/jandan_spider)时自然遇到了不少的困难,在这里把我的学习过程记录一下:
这是源代码:
import os
import requests
from bs4 import BeautifulSoup
import argparse
import ast
import atexit
import multiprocessing
parser = argparse.ArgumentParser(description='Spider for jiandan.net')
parser.add_argument('--page', dest='page', action='store', default=5, type=int, help='max page number')
parser.add_argument('--dir', dest='dir', action='store', default='images', help='the dir where the image save')
args = parser.parse_args()
page = args.page
_dir = args.dir
if not os.path.exists(_dir):
os.mkdir(_dir)
headers = {'referer': 'http://jandan.net/',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0'}
image_cache = set()
if os.path.exists(".cache"):
with open('.cache', 'r') as f:
image_cache = ast.literal_eval(f.read(-1))
@atexit.register
def hook():
with open('.cache', 'w+') as f:
f.write(str(image_cache))
index = len(image_cache)
# 保存图片
def save_jpg(res_url):
global index
html = BeautifulSoup(requests.get(res_url, headers=headers).text, features="html.parser")
for link in html.find_all('a', {'class': 'view_img_link'}):
if link.get('href') not in image_cache:
with open(
'{}/{}.{}'.format(_dir, index, link.get('href')[len(link.get('href')) - 3: len(link.get('href'))]),
'wb') as jpg:
jpg.write(requests.get("http:" + link.get('href')).content)
image_cache.add(link.get('href'))
print("正在抓取第%s条数据" % index)
index += 1
if __name__ == '__main__':
url = 'http://jandan.net/ooxx'
for i in range(0, page):
save_jpg(url)
ahref = BeautifulSoup(requests.get(url, headers=headers).text, features="html.parser").find('a', {'class': 'previous-comment-page'})
if ahref is None:
print('no more page')
exit(0)
else:
url = "http:" + ahref.get('href')
首先是作者用到了几个库我没有见过:
import argparse
import ast
import atexit
import multiprocessing
经过查阅得知:
argparse模块
是“命令行选项、参数和子命令解析器”,简单来说,就是用来实现命令行操作的,我们都知道,通常我写python都是在pycharm里面,写好代码后点击run就运行了。如果要在命令行中运行已经写好的.py模块,就要输入“python name.py”来运行name.py模块,这个时候我们写的name.py模块是没有参数的,如何让它像一般的命令那样,可以通过接收从命令行输入的参数来改变自己的行为呢,argparse模块就可以做到。而我本人初步学习该模块也是通过Argparse 教程来学习的。作者在本项目中添加了两个命令行参数:--dir
和--page
,这两个参数分别指明了图片存放的位置和要爬取的页数,也说明本爬虫需要以命令行的方式来执行(可能原作者是在linux下开发的?)。
ast模块
ast是“抽象语法树”,官方文档写的太晦涩,对于没有接触过编译原理的同学来说不是很友好,我参考了这篇博客大致熟悉了一下相关的概念,但本爬虫项目里面貌似只是借用了ast模块的一个评估函数而已,所以就没有继续深入学习。作者在这里使用的目的应该是方便多次爬取避免重复或者代码复用之类的。
atexit — 退出处理器
顾名思义,该模块的作用在于,python解释器退出的时候执行一些操作,作者使用了该模块注册了一个hook()函数,实现了在程序退出的时候把image_cache里面的内容写到.cache文件中去,是一个退出时保存文件的操作,就不用在程序中显式调用hook()函数。image_cache里面存放的自然是与图片相关的东西,我们先往后看。
爬取图片的函数
# 保存图片
def save_jpg(res_url):
global index
html = BeautifulSoup(requests.get(res_url, headers=headers).text, features="html.parser")
for link in html.find_all('a', {'class': 'view_img_link'}):
if link.get('href') not in image_cache:
with open(
'{}/{}.{}'.format(_dir, index, link.get('href')[len(link.get('href')) - 3: len(link.get('href'))]),
'wb') as jpg:
jpg.write(requests.get("http:" + link.get('href')).content)
image_cache.add(link.get('href'))
print("正在抓取第%s条数据" % index)
index += 1
我们来分析该函数:参数res_url
是要爬取图片的网址;index
是image_cache
的长度(参看开头源代码);
html = BeautifulSoup(requests.get(res_url, headers=headers).text, features="html.parser")
这行代码的意思是使用requests.get()
方法获取网页,然后构造BeautifulSoup
对象来对网页进行分析;为了分析下面的代码,我们先打开目标网址(http://jandan.net/ooxx):
这是我们要爬取的妹子图片,我们(Ctrl+U)查看该网页的源代码:
可以看到,我们的一个图片项位于<li></li>
标签之间,而我们的图片地址在<a href=...>
标签里面(<img src=...>标签里也有,都能获取到
),这样我们可以利用BS4的findall()
函数来获取全部的含有图片地址的<a>
标签:
for link in html.find_all('a', {'class': 'view_img_link'}):
if link.get('href') not in image_cache:
with open(
'{}/{}.{}'.format(_dir, index, link.get('href')[len(link.get('href')) - 3: len(link.get('href'))]),
'wb') as jpg:
jpg.write(requests.get("http:" + link.get('href')).content)
image_cache.add(link.get('href'))
print("正在抓取第%s条数据" % index)
index += 1
这里涉及到了保存图片的操作。首先为了防止获取同样的图片,作者使用了一个集合(set)即image_cache
来暂存图片地址;获取到含有图片地址的<a>
标签之后,作者将其赋值给link
变量;调用link
对象的get()
方法获取图片的URL,再用requests.get()
方法获取这个图片,将图片保存到本地(这里图片的命名方法有些复杂,就先不研究了)。
这行代码image_cache.add(link.get('href'))
,解决了之前的疑问:image_cache
里面存放的是图片的链接,而hook()
函数的作用是在程序退出之时保存这些链接到.cache
文件中去,方便下次直接获取这些图片(是个好习惯)。
主函数
现在我们来分析主函数代码:
if __name__ == '__main__':
url = 'http://jandan.net/ooxx'
for i in range(0, page):
save_jpg(url)
ahref = BeautifulSoup(requests.get(url, headers=headers).text, features="html.parser").find('a', {'class': 'previous-comment-page'})
if ahref is None:
print('no more page')
exit(0)
else:
url = "http:" + ahref.get('href')
这里的变量page
是通过命令行来赋值的(参见开头源代码和argparse模块介绍),url
是不断更新的,我们来研究这行代码:
ahref = BeautifulSoup(requests.get(url, headers=headers).text, features="html.parser").find('a', {'class': 'previous-comment-page'})
我们把网页拉到最下面:
发现是倒着排序的,开头是134页,点击下一页之后展示的是第133页,我们根据这个来调整页数。
我们查看源代码(F12工具还不熟练,我是肉眼找到这一块的):
可以看到,下一页的链接存放在 <a title="Older Comments" href="//jandan.net/ooxx/MjAyMDA3MDYtMTMz#comments" class="previous-comment-page">
标签里面,同上面一样,作者用BS4来找到它,提取下一页的网址并更新url变量:
if ahref is None:
print('no more page')
exit(0)
else:
url = "http:" + ahref.get('href')
这样,就大功告成啦!
运行测试
我们把源代码copy到本地,保存到1.txt中去,将扩展名改为1.py,打开Anaconda Prompt,执行指令python C:\Users\DGX\Desktop\1.py --dir D:\ --page 5
,解果运行出错:
应该是编码错误,我由在代码前面加上一行# coding=utf-8
来声明编码类型,接着运行,图样图森破,还是出错了:
依然是编码问题,我把这个错误百度了一下,发现直接用txt保存是不行的,因为txt默认ANSI编码,把它另存为的时候改成utf-8就行了(我还把刚刚加上去的# coding=utf-8
给删除了),另存为1.py,运行成功:
我输入的保存地址在D盘,结果是这样的:
99张妹子的图片都保存在D盘啦(忘记建个文件夹了Orz)!芜湖!