0. 基本事项
0.1 基本原则
- 编写爬虫之前查找网络上是否有源码,测试是否可用(请求连接是否有效),可用则用
- 优先选择API接口,其次为HTML链接,理由是虽然寻找API链接需要花较多时间,但是处理HTML的不规整数据同样也耗费时间
- 手机上的信息电脑端看不到时不一定没有适配电脑端,需要自己去找,或者去查一下,手机端信息电脑端一般都有
0.2 注意事项
- 找到对应请求链接后,一定要确保访问的header正确,需要通过查看网页请求详情去找这个header
- 请求头中的Accept-Encoding是设置返回的编码,一般不需要设置,如果返回的数据无法decode,一般是这个地方设置不当
- 如果报错400,代表请求格式错误,先打印url,然后在浏览器访问url尝试url有效性,然后再去找其他错误,如cookie过期等
- 如果要获取网页请求链接,不要使用Google浏览器,Google浏览器隐藏了网页请求连接详情,可以用Microsoft Edge浏览器或者360浏览器查看,特别是查看请求接口
0.3 爬虫示例
0.3.1 微博爬虫
- Github:WeiboSpider
1 链接类别
1.1 API接口
数据规整
内容详细
比较难获得
1.2 HTML链接
- 所见即所得,HTML里面有的元素原则上都可以获取(除非有反爬机制)
- 万能办法
- 获取对应数据比较麻烦
2 链接获取
2.1 神器:浏览器开发者工具查看网络请求
- Microsoft Edge浏览器与360浏览器使用F12可看(或者右键选择开发者工具),Google新版浏览器不可以看
2.2 查看HTML请求
2.3 查看API请求
3 链接请求
3.1 必要的headers信息
- user-agent:伪装浏览器代理,避免被服务器识别成爬虫
- cookie:网站请求的必要信息,代表请求者信息
- 可以在网络请求中查看对应的设置,除了user-agent和cookie必要,其他信息可有可无,如果请求返回代码是”4XX”(如请求返回码是401,代表要求身份认证,需要添加cookie),可以依据返回码信息适当再添加信息。事实上,一般有user-agent和cookie必要信息即可,如果报错一般是其他地方的原因。
- HTTP状态码解释:菜鸟教程
3.2 模拟请求
3.2.1 基于requests
- GET请求:基本适用大多数的爬虫场景
import requests
# Method1: 使用params参数
url = "https://www.sogou.com/tx?"
key_dict = {"query": "python"} # 视情况而定
headers = {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36",}
response = requests.get(url, params=key_dict, headers=headers)
# 查看请求结果
print(response.content.decode('utf-8'))
# Method2: 不使用params参数
url = "https://www.sogou.com/tx?query=python" # GET请求可以直接在链接后加参数
headers = {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36",}
response = requests.get(url, headers=headers)
# 查看请求结果,注意此处是response.content.decode('utf-8'),采用content获取
print(response.content.decode('utf-8'))
### response.text与response.content区别 ###
# text返回的是Unicode型的数据
# content返回的是是二进制的数据。
# 总结:如果想取文本,可以通过r.text;如果想取图片和文件,则可以通过r.content
- POST请求:POST一般需要设置好请求参数,使用场景较少,并且获取对应参数比较复杂
import requests
import time
import json
url = "https://fanyi.qq.com/api/translate"
headers = {
"Origin": "https://fanyi.qq.com",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36",
}
time_str = str(int(1000 * time.time()))
key_dict = {
"source": "zh",
"target": "en",
"sourceText": "发送 POST 请求",
"sessionUuid ": "translate_uuid" + time_str
} # 必要的POST请求参数
response = requests.post(url, data=key_dict, headers=headers)
# 查看请求结果
print(response.status_code)
result = response.json() # response自带json解码函数
print(result)
3.2.2 基于urllib
- urllib介绍
urllib.request :最基本的http请求模块,用来模拟发送请求。
urllib.error :异常处理模块。
urllib.parse :工具模块,提供了许多URL处理方法,如:拆分、解析、合并等。
urllib.robotparser :解析网站的robots.txt文件。
- GET请求
import urllib.request # 注意正确的imprt格式
import urllib.parse
dict = {'color':'red','number':7} #随便写的参数数据
params = urllib.parse.urlencode(dict)
url = "http://baidu.com?%s" % params #GET方法的参数在url后面,也可以不带参数,此时默认为GET方法
add = urllib.request.Request(url, header=headers)
resp = urllib.request.urlopen(add, data=None) # 不设置data参数,使用GET请求
# 查看请求结果,注意此处是 resp.read().decode("utf-8"),采用read函数获取
print(resp.read().decode("utf-8"))
- POST请求
import urllib.request
import urllib.parse
dict = {'color':'red','number':7}
params = urllib.parse.urlencode(dict)
params = params.encode('ascii')
resp = urllib.request.urlopen("http://baidu.com",data=params) # 设置data参数,使用POST请求
# 查看请求结果
print(resp.read().decode("utf-8"))
4 处理返回结果
4.0 API接口返回结果与HTML链接返回结果
API接口返回的链接十分规整,通过json解码即可获取
HTML链接返回的结果杂乱,需要借助工具,后续均针对处理HTML链接返回结果
4.1 BeautifulSoup获取指定信息
4.1.1 基本示例
import urllib.request
from bs4 import BeautifulSoup
html = urllib.request.urlopen("http://www.pythonscraping.com/pages/page1.html")
# bsObj = BeautifulSoup(html.read()) # 会警告
bsObj = BeautifulSoup(html.read(),'html.parser') # 正确写法,注意添加parser格式 html.parser
- 此处用的是urllib.request.urlopen所以需要用read()函数读取返回结果
4.1.2 查找、选择、删除标签,获取标签属性、文本等
- 本节内容来自博客:BeautifulSoup查找、选择、删除标签,获取标签属性、文本等
- 新建BeautifulSoup对象
from bs4 import BeautifulSoup
soup = BeautifulSoup(html.read(), features="html.parser")
- 查找
# 根据标签查找
imgs = soup.find_all("img")
# 根据属性查找
imgs = soup.find_all("img", attrs={"class": "avatar"})
# 根据样式查找(支持正则)
tabs = soup.find_all(style=re.compile(r'.*display:none.*?'))
- 获得标签的属性
# 获取img标签的src属性
imgs = soup.find_all('img')
for img in imgs:
url = img.get("src")
print(url)
- 获取标签内文本
# 获取文本(分隔符、去除空白)
soup.get_text(separator=" ", strip=True)
- 根据CSS选择器选择标签
#获取a标签中具有href属性的标签
soup.select('a[href]')
- 正则匹配标签
# 选取所有的h标签,替换内容(去除h标签内的标签,只保留文本)
for i in soup.find_all(re.compile("^h[1-6]")):
i.string = i.get_text()
- 删除标签
# 删除style中包含隐藏的标签
for i in self.soup.find_all(style=re.compile(r'.*display:none.*?')):
i.decompose()
- 去除空标签
# 删除空白标签(例如<p></p>、<p><br></p>, img、video、hr除外)
soup = BeautifulSoup(clean_content, features="html.parser")
for i in soup.find_all(lambda tag: len(tag.get_text()) == 0 and tag.name not in ["img", "video", "br"] and tag.name != "br"):
for j in i.descendants:
if j.name in ["img", "video", "br"]:
break
else:
i.decompose()
4.2 正则表达式
- 有时候HTML链接返回结果中的信息无法通过Beautifulsoup处理,可以通过正则表达式处理
4.2.1 示例
import re # 正则表达式的依赖包
import urllib.request
# 请求链接
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36',
'Cookie': cookie # cookie自行获取,方法可见上文
}
uid = 1906123125
url = "https://weibo.com/%s?is_hot=1" % (uid)
add = urllib.request.Request(url=url, headers=headers)
# 获得请求结果
page = urllib.request.urlopen(url=add, timeout=20).read().decode(encoding="utf-8")
# 正则查找
str1= 'div111111111'
grade_info = re.findall(r'div', page) # 一般用findall函数即可,page代表待查找的内容
# 查看结果
print('page:', page)
print('grapde info:', grade_info)
- 详细的正则表达式教程查看:菜鸟教程,重点是《正则表达式模式》
5 存储爬取数据
5.1 sql语句(推荐)
优点:数据比较规整,且可以便于判断信息是否已经爬取,以方便设置断点,下次可以继续爬取
缺点:不能直接用于数据分析,需要使用python获取指定条件下的数据
使用如下的sql工具即可
#-*- coding: utf-8 -*-
from DBUtils.PooledDB import PooledDB
import pymysql
from utils.config import mysql_info
class DB_POOL():
__pool = None
def __init__(self,db_name):
self.__pool = PooledDB(pymysql, mincached=1, maxcached=20, host=mysql_info['host'], user=mysql_info['user'],
passwd=mysql_info['passwd'], db=db_name, port=3306, setsession=['SET AUTOCOMMIT = 1'],
cursorclass=pymysql.cursors.DictCursor, charset='utf8mb4')
def to_connect(self):
return self.__pool.connection()
def is_connected(self, conn):
"""Check if the server is alive"""
try:
conn.ping(reconnect=True)
# print("db is connecting")
except:
conn = self.to_connect()
print("db reconnect")
return conn
def __getInsertId(self, cursor):
cursor.execute('select @@IDENTITY AS id')
result = cursor.fetchall()
return result[0]['id']
def __query(self, sql, param=None):
conn = self.__pool.connection()
cursor = conn.cursor()
if param is None:
count = cursor.execute(sql)
else:
count = cursor.execute(sql, param)
return count, cursor
def getAll(self, sql, param=None):
# param查询条件值(元组\列表)
# 返回list\boolean
count, cursor = self.__query(sql, param)
if count >= 1:
res = cursor.fetchall()
else:
res = False
return res
def getOne(self, sql, param=None):
count, cursor = self.__query(sql, param)
if count >= 1:
res = cursor.fetchone()
else:
res = False
return res
def getMany(self, sql, num, param=None):
count, cursor = self.__query(sql, param)
if count >= 1:
res = cursor.fetchmany(num)
else:
res = False
return res
def insertOne(self, sql, value):
conn = self.__pool.connection()
cursor = conn.cursor()
cursor.execute(sql, value)
return self.__getInsertId(cursor)
def update(self, sql, param=None):
return self.__query(sql, param)[0]
def insert_dict(self, dic, table_name):
placeholder = ','.join(['%s' for i in range(len(dic.values()))])
sql = f"insert into {table_name} ({','.join(dic.keys())}) values ({placeholder})"
# print('sql', sql)
return self.insertOne(sql, tuple(dic.values()))
db = DB_POOL('weibo')
5.2 csv文件
- 基于pandas操作即可
- 优点:csv数据可以直接用于数据分析
- 缺点:会遇到数据乱码或乱序或缺失等问题
- 局限:如果不是一条一条数据存储,csv文件可能需要较大的内存