网 络 爬 虫?
-模拟客户端(浏览器,电脑和手机APP)发送请求并获取响应
按照规则提取数据
-自动批量采集网络资源
关于我对写爬虫的讨论
- url (知道url地址的规律和页码数:构造url地址的列表,或者start_url)
- 发送请求,获取响应 (requests)
- 提取数据 (返回json字符串用json,返回html用lxml和xpath
- 保存
HTTP?HTTPS?
-HTTP是超文本传输协议,以明文的形式传输,效率高,不安全
-HTTPS是HTTP+SSL(安全套接字层),传输前加密,之后解密,效率低,安全
-HTTP之请求头
(F12再F5点击任意一个请求,再点击headers,我用的是谷歌)
requests 模块的学习
- 发送get,post请求
response=requests.get(url)
response=requests.post(url,data={请求体的字典})
- 获取网页源码
-response.text
常出现乱码,可在后面加上resonse.enconding=‘utf-8’
-response.content.decode()
将响应的二进制字节流转化为str类型
获取网页源码的正确方式
- response.content.decode()
- response.content.decode(‘gbk’)
- response.text
发送带headers的请求
#可以带上更多的键值对
headers={"user-agent":" Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1","referer": "https://fanyi.baidu.com/?aldtype=16047"}
超时参数
有时候由于网络波动,请求不成功,需要设置超时参数,在规定时间结束请求
response=requests.get(url,headers=headers,timeout=3)
#在三秒必须返回响应否则报错
但是往往面对没有响应我们会去多次请求,这就需要用到retrying模块
retrying模块的学习
基本使用方法
from retrying import retry
#下面函数的最大报错次数是3,即是错三次才会报错
@retry(stop_max_attemp_number=3)
def fun1:
print("no")
raise ValueError("erroe")
处理Cookie相关请求
我登录的是安大教务处,有两种方法
- 直接携带cookie请求url(就是必须进入要登录的网站,这时候的url就是地址栏的url,cookie就在左边的第一个包里面,用get)
1)可以写成csharpheaders={...,"Cookie": "JSESSIONID=2A8EDAD25C766D03B05FCFB381234A1C.TM2"}
2)可以写成response=res.get(url,headers=headers,cookies=cookie_dict)
- 先发送post请求,获取cookie,再带上cookie一起请求登录后的页面
session=requests.session()
#session具有的方法和requests一样
session.post(url,data.headers)
#这使服务器设置再本地的cookie会存在session
session.get(url,headers)
#会带上之前保存在session里面的cookie,可以请求成功
感觉这个方法好麻烦,直接把cookie写到headers里面它不香吗
数据提取方法
就是从我们得到的源码中提取数据,有很多种方法,可以用正则,beatifulsoup,json,lxml
json:看起来像python的列表 或字典
-
数据交换格式:数据从后端传到前端的过程叫数据交换,数据交换的时候我们往往会使用json的格式
-
哪里会返回json数据
-浏览器切换到手机版
-抓包app
#可以把json字符串转化成python类型
import json
json loads(json字符串)
把python类型转化为json字符串
json.dumps({'a':'a'})
#但写文件到本地时,是不可以把字典写入的,所以我们可以用json.dumps
json.dumps(json_str,ensure_ascii=False,indent=2)
#ensure_ascii:让中文显示中文
#indent(n):能让下一行在上一行的基础上多n个空格
xpath和lxml
xpath:一门从html里面提取数据的语言
xpath语法
xpath helper:帮助我们从elements中定位数据
- 如何让选择标签
'/'能够选中html下head下(一级)的所有的meta标签
/html/head/meta
'//'能从任意节点开始选择
//li :当前页面上所有的li标签
//html//head//link:head下的所有(多级)link标签
./是当前节点
@符号的用途
1.选择具体某个元素
选择class='xh'的dic下的ul下li
//dic[@class='xh']/ul/li
2.选择a的href的值
a/@href
获取文本
获取a的文本
/a/text()
获取a下的所有文本
/a//text()
-lxml
注意:由于element的元素和response的元素一般不一样,所以我们在用xpath提取元素的时候看到的标准是在response里面的
from lxml import etree
element=etree.HTML('html字符串')
element.xpath(' ')
上手的第二个程序-糗事百科
xpath
# -*- coding: utf-8 -*-
"""
Created on Mon Feb 10 04:12:46 2020
@author: acer
"""
import requests
from lxml import etree
import json
class QIUSHISPIDER:
def __init__(self):
self.headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"}
self.url='https://www.qiushibaike.com/hot/page/{}/'
def get_url_list(self):#根据url的规律,构造url_list
url_list=[self.url.format(i) for i in range(1,14)]
return url_list
def parse_url(self,url):
response=requests.get(url,headers=self.headers)
return response.content.decode()
def get_content(self,html_str):
e_html=etree.HTML(html_str)
e_html_div=e_html.xpath('//div[@id="content-left"]/div')
# print(e_html_div)
content_list=[]
for e in e_html_div:
item={}
#用这个三元推导式是为了判断列表中的元素是否不为零,因为如果为零会出现错误
#str.strip() 可以消除字符串中首尾出现的字符,默认为空格
item['author']=e.xpath('.//h2/text()')[0].strip() if len(e.xpath('.//h2/text()'))>0 else None
item['content']=e.xpath('.//div[@class="content"]/span//text()')
item['content']=[i.strip() for i in item['content']]
item['img']=e.xpath('.//img/@src')
item['img']=[i.strip() for i in item['img']]
content_list.append(item)
return content_list
def save_content(self,html_str):
for content in self.get_content(html_str):
with open("quishi.txt","a",encoding="utf-8") as f:
print("保存成功")
f.write(json.dumps(content,ensure_ascii=False))
f.write('\n')
def run(self):
#根据url的规律,构造url_list
for url in self.get_url_list():
html_str=self.parse_url(url)
#对每个url发送请求,获取响应
self.save_content(html_str)
#提取数据
#保存
if __name__ =='__main__':
quishi=QIUSHISPIDER()
quishi.run()
上手的第二个程序-手机版豆瓣
json
import json
import paser
class Doubanspider:
#初始化方法,初始化变量
def __init__(self):
self.url="https://m.douban.com/rexxar/api/v2/subject_collection/tv_american/items?os=ios&for_mobile=1&start={}&count=18&loc_id=108288&_=1581225562659"
#提取数据,得到的是一个列表
def get_needcontent(self,get_response):
dict_url=json.loads(get_response)
needcontent=dict_url['subject_collection_items']
total=dict_url["total"]
return needcontent,total
#保存数据到一个文本里面,注意这是以追加的方式打开
def save_content(self,needcontent):
with open("Douban.txt","",encoding="utf-8") as f:
for content in needcontent:
f.write(json.dumps(content,ensure_ascii=False))
f.write('\n')
print("保存成功")
#实现主程序
def run(self):
total=100
num=0
#应该加上18
while num<total+18:
#1,获取start_url
url=self.url.format(num)
print(url)
#2.发送请求,获取响应
get_response=paser.parse_url(url)
#3.提取所需的数据
needcontent,total=self.get_needcontent(get_response)
#4.保存
self.save_content(needcontent)
#5.构造下一页数据,循环2-4
num=num+18
#测试程序
if __name__ =='__main__':
#实例化一个方法
doban=Doubanspider()
doban.run()
上手的第一个爬虫程序–下载小说
import requests as res
#再见了正则,拥抱我的SP,哈哈哈哈
from bs4 import BeautifulSoup as bs
import re
#download a website 必须加引号 注意headers是字典
url='http://www.booksky.cc/299933.html'
headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36"}
#使用get发送请求为了更真实模拟浏览器加上headers
#获取网页源码
response=res.get(url,headers=headers)
#这时候获得的请求其实只是数字,需要将网页编码方式改成utf-8
#必须要设置正确的编码和文本属性 编码也要打上引号
response.encoding='utf-8'
#这时候我们就获得了html字符串
html=response.text
#print(html)
#将网页内容解析到sp里面,sp是bs类的对象
sp=bs(html,"html.parser")
#获取标题
ti=sp.title.text[0:8]
#print(ti)
#再获取每一章的信息
cha_info_list=re.findall(r'<li class="hide"><a href="(.*?)" title="(.*?)" target="_blank">',html,re.S)
#新建一个文件,保存小说内容
with open('{}.txt'.format(ti),'w',encoding='utf-8') as f:
#循环每一个章节,分别去下载
for cha in cha_info_list:
# cha_url=cha[0]
# cha_ti =cha[1] 不优雅
cha_url,cha_ti=cha
cha_url="http://www.booksky.cc{}".format(cha_url)
# print(cha_url,cha_ti)
#接下来就可以下载章节内容了
cha_response=res.get(cha_url,headers=headers)
cha_response.encoding='utf-8'
cha_response_html=cha_response.text
# sp1=bs(cha_response_html,"html.parser")
# cha_con=sp1.text
# cha_temp=re.findall(r'Sitemap(.*?) })();',cha_con,re.S)
# print(cha_temp)#这个地方本来是想要尝试一下bs的,可以是可以但是后面又多了一些东西,我也搞不掉,所以使用正则
cha_ti=re.findall(r'<title>(.*?)</title>',cha_response_html,re.S)[0]
cha_con=re.findall(r'<div class="content" id="chaptercontent">(.*?)</div>',cha_response_html,re.S)[0]
# 清洗数据
cha_con=cha_con.replace(' ','')
cha_con=cha_con.replace('<br/>','')
cha_con=cha_con.replace(' ','')
f.write(cha_ti)
f.write(cha_con)
f.write('\n\n\n')
不满意的地方:
这是一本连载小说,所以应该去检查是否更新?
到底该怎么把bs用的更好?
有时候下载书的章节时,要求换页怎么处理?
一开始去晋江下载书时出现的乱码该怎么解决?