教程中的项目请跟着在pycharm中写一遍,注意查看注释内容。推荐的课外练习请自行完成,完成后再查看参考代码。
本章知识点:
- python基础
- 使用requests库发送请求
- 使用beautifulsoup解析网页源码
python基础学习
本教程爬虫开发需要python语言基础,请根据自身情况选择:
-
我是零基础:请先学习python基础教程,强烈推荐:廖雪峰的python教程。
- 重点学习章节:python基础、函数、高级特性、模块、面相对象编程、错误、调试和测试、IO编程。
- 初步了解章节:进程和线程、正则表达式、常用内建模块、常用第三方模块、virtualenv、网络编程、Web开发。这些内容十分重要,但初期涉及不多,需要时我会提醒读大家回顾。
- 暂时跳过章节:面向对象高级编程、函数式编程、图形界面、电子邮件、访问数据库、异步IO、实战,这些内容会在Python爬虫-高级进阶系列教程涉及。
- 学习时间:2~3天。
-
我有编程基础:直接开始爬虫教程。
第一个爬虫程序:游戏葡萄
创建一个game_grape.py文件,写入代码:
from urllib import request # 导入urllib中的request模块
response= request.urlopen('http://youxiputao.com/') # 用urlopen()函数执行一次请求,地址是游戏葡萄的首页,并把返回对象赋值给response变量。
html = response.read().decode() # 从response中读取结果,解码成str类型的字符串,就是我们的html源码。
print(html) # 在控制台打印html源码。
执行程序,控制台打印出首页的html源码:
程序运行成功,超级简单有木有!
其实爬虫的本质就是:发起请求,获得响应,解析结果。企业中千万级爬虫、分布式爬虫都是在这个基础上抓取得更快、存储得更多、突破各种反爬虫措施而已。
使用requests进行抓取
使用python内置的urllib包虽然可以进行抓取,但使用非常不方便,特别是面对复杂的网络请求时,使用起来会力不从心。我们来尝试用第三方网络请求工具requests
重写爬虫。
import requests
response = requests.get('http://youxiputao.com/')
print(response.text)
执行程序,报错:
requests是第三方库,需要安装后使用:按住Win+R,输入powershell(Win10以下输入cmd),按回车键,输入命令pip install requests
,如图:
第一次安装需要下载,如果速度太慢安装失败,可以把下载源更换为清华大学镜像:输入pip -V
命令,查看pip版本:
- 高于10.0版本,执行命令:
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
- 低于10.0版本,参考教程:设置pip安装源为国内清华大学镜像
换源之后重新安装requests
,出现Successfully installed xxx即为安装成功。
再次执行代码,成功打印出html源码:
源码解析
我们想解析网页源码,获得新闻标题和链接,使用第三方库BeautifulSoup
解析工具:pip install bs4
。打开Chrome浏览器,右键点击新闻标题,点击检查,查看新闻标题的位置:
根据源码位置找到标题,如图:
修改代码:
import requests
from bs4 import BeautifulSoup
response = requests.get('http://youxiputao.com/') # 发起网络请求,获得响应
soup = BeautifulSoup(response.text, 'html.parser') # 使用BeautifulSoup解析,查找我们想要的内容。html.parser是系统内置的解析方案,可以不填但是会报警告(warning)
ul_node = soup.find('ul', {'class': 'news-list'}) # 查找class值为news-list的ul标签,
li_node = ul_node.find('li') # 在ul标签下查找第一个li标签
h4_node = li_node.find('h4') # 在li标签下查找h4标签
a_node = h4_node.find('a') # 查找a标签
print(a_node.text) # 打印出a标签内容
print(a_node['href']) # 打印出a标签链接(href属性的值)
成功解析出第一条新闻标题和链接:
咦,情况不对,这个链接怎么看起来很别扭?而且只拿到第一条新闻,剩下的怎么办呢?继续修改代码:
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin #从urllib库导入urljoin()函数,用于url拼接
response = requests.get('http://youxiputao.com/')
soup = BeautifulSoup(response.text, 'html.parser')
for li_node in soup.find('ul', {'class': 'news-list'}).find_all('li'): # 使用find_all()找出所有li标签,在for循环中解析每个<a>标签
a_node = li_node.find('h4').find('a') # find()函数返回标签,可以连续查找;find_all()返回的是符合条件的所有标签的数组,需要循环遍历
title = a_node.text
href = a_node['href']
url = urljoin(response.url, href) # response.url是响应的地址,不一定是原始请求地址哦,因为网站可能会把对请求做重定向
print(url, title)
第一页采集成功:
拿到了详情页面的链接,我们继续发送请求,采集详细数据。随便点开一个新闻页面,在chrome中按F12键查看页面结构:我们需要标题、时间、正文三部分,并把它们放在一个字典里来表示这条数据。
在刚才代码后面继续添加:
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin # 从urllib库导入urljoin()函数,用于url拼接
response = requests.get('http://youxiputao.com/')
soup = BeautifulSoup(response.text, 'html.parser')
for li_node in soup.find('ul', {'class': 'news-list'}).find_all('li'): # 使用find_all()找出所有li标签,在for循环中解析每个<a>标签
a_node = li_node.find('h4').find('a') # find()函数返回标签,可以连续查找;find_all()返回的是符合条件的所有标签的数组,需要循环遍历
href = a_node['href']
url = urljoin(response.url, href) # response.url是响应的地址,不一定是原始请求地址哦,因为网站可能会把对请求做重定向
response_detail = requests.get(url) # 用response_detail 来区别之前的response
soup_detail = BeautifulSoup(response_detail.text, 'html.parser') # 继续解析
title = soup_detail.find('h2', class_='title').text # 可以用class_参数定位标签,仅限包含class、id等字段的标签,字典写法更通用
publish_time = soup_detail.find('div', {'class': 'pull-left'}).text # 直接获取div标签的“text”属性,包含div标签下所有子标签的文本
article = soup_detail.find('div', {'class': 'info-box col-sm-12'}).text
data = {'title': title, 'publish_time': publish_time, 'article': article}
print(data)
执行程序,采集结果如下:
查看结果,发现有两个问题:
- 混进了奇怪的“\n”字符:\n是换行字符,对于字符串首位的空白字符(\n,\t,空格),可以使用
trip()
函数剔除。 - 图片去哪儿了:真实的爬虫项目通常不会采集图片,原因很简单:大型网站上千万数据,每条数据几张图片,再多硬盘都装不下。企业中对图片常用的处理方案:保存图片链接。
继续修改刚才的代码:
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
response = requests.get('http://youxiputao.com/')
soup = BeautifulSoup(response.text, 'html.parser')
for li_node in soup.find('ul', {'class': 'news-list'}).find_all('li'):
a_node = li_node.find('h4').find('a')
href = a_node['href']
url = urljoin(response.url, href)
response_detail = requests.get(url)
soup_detail = BeautifulSoup(response_detail.text, 'html.parser')
title = soup_detail.find('h2', class_='title').text
publish_time = soup_detail.find('div', {'class': 'pull-left'}).text.strip()
article = soup_detail.find('div', {'class': 'info-detail'}).text.strip() # strip()函数可以过滤字符串首尾的空白字符
images = [] # 用于存放文章中的图片
for img_node in soup_detail.find('div', {'class': 'info-detail'}).find_all('img'): # 找到文章主体节点,继续找到所有<img>图片标签
img_url = img_node['src']
images.append(img_url)
cover = soup_detail.find('div', class_='cover').find('img')['src'] # 封面图片
images.append(cover)
data = {'title': title, 'publish_time': publish_time, 'images': images, 'article': article}
print(data)
看看修改后的采集结果:
图片有了,文章开头也没有含换行符了,感觉自己太牛X了!仔细查看字段内容,发现文章中间仍然包含\n,一般来说\n字符可以用来标识段落,不用处理。如果必须处理的话可以使用replace()函数:
article = article.replace('\n', '') # 使用“空字符”来替换“空白字符\n”,并重新赋值给article变量
最后,我们用面向对象的方式改写程序,完整代码如下:
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
class GameGrapeSpider: # 声明一个叫“GameGrapeSpider(游戏葡萄爬虫)”的类
def start(self): # 爬虫的启动函数
response = requests.get('http://youxiputao.com/')
soup = BeautifulSoup(response.text, 'html.parser')
for li_node in soup.find('ul', {'class': 'news-list'}).find_all('li'):
a_node = li_node.find('h4').find('a')
href = a_node['href']
url = urljoin(response.url, href)
response_detail = requests.get(url)
soup_detail = BeautifulSoup(response_detail.text, 'html.parser')
title = soup_detail.find('h2', class_='title').text
publish_time = soup_detail.find('div', {'class': 'pull-left'}).text.strip()
article = soup_detail.find('div', {'class': 'info-detail'}).text.strip()
images = [] # 用于存放文章中的图片
for img_node in soup_detail.find('div', {'class': 'info-detail'}).find_all('img'):
img_url = img_node['src']
images.append(img_url)
cover = soup_detail.find('div', class_='cover').find('img')['src'] # 封面图片
images.append(cover)
data = {'title': title, 'publish_time': publish_time, 'images': images, 'article': article}
print(data)
if __name__ == '__main__': # 程序入口,直接运行game_grape.py才执行,被导入时不会执行
spider = GameGrapeSpider() # 创建一个对象
spider.start() # 启动爬虫
课外练习:抓取以下网站的首页内容
练习答案:
Github地址
下一章 >> Python爬虫入门到入职03:全量抓取
网友评论