爬虫教程


0. 基本事项

0.1 基本原则

  1. 编写爬虫之前查找网络上是否有源码,测试是否可用(请求连接是否有效),可用则用
  2. 优先选择API接口,其次为HTML链接,理由是虽然寻找API链接需要花较多时间,但是处理HTML的不规整数据同样也耗费时间
  3. 手机上的信息电脑端看不到时不一定没有适配电脑端,需要自己去找,或者去查一下,手机端信息电脑端一般都有

0.2 注意事项

  1. 找到对应请求链接后,一定要确保访问的header正确,需要通过查看网页请求详情去找这个header
  2. 请求头中的Accept-Encoding是设置返回的编码,一般不需要设置,如果返回的数据无法decode,一般是这个地方设置不当
  3. 如果报错400,代表请求格式错误,先打印url,然后在浏览器访问url尝试url有效性,然后再去找其他错误,如cookie过期等
  4. 如果要获取网页请求链接,不要使用Google浏览器,Google浏览器隐藏了网页请求连接详情,可以用Microsoft Edge浏览器或者360浏览器查看,特别是查看请求接口

0.3 爬虫示例

0.3.1 微博爬虫

1 链接类别

1.1 API接口

  • 数据规整

  • 内容详细

  • 比较难获得

1.2 HTML链接

  • 所见即所得,HTML里面有的元素原则上都可以获取(除非有反爬机制)
  • 万能办法
  • 获取对应数据比较麻烦

2 链接获取

2.1 神器:浏览器开发者工具查看网络请求

  • Microsoft Edge浏览器与360浏览器使用F12可看(或者右键选择开发者工具),Google新版浏览器不可以看

2.2 查看HTML请求

查看HTML请求链接

2.3 查看API请求

查看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

requests返回结果属性

  • 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 查找、选择、删除标签,获取标签属性、文本等

  1. 新建BeautifulSoup对象
from bs4 import BeautifulSoup
soup = BeautifulSoup(html.read(), features="html.parser")
  1. 查找
# 根据标签查找
imgs = soup.find_all("img")
# 根据属性查找
imgs = soup.find_all("img", attrs={"class": "avatar"})
# 根据样式查找(支持正则)
tabs = soup.find_all(style=re.compile(r'.*display:none.*?'))
  1. 获得标签的属性
# 获取img标签的src属性
imgs = soup.find_all('img')
for img in imgs:
    url = img.get("src")
    print(url)
  1. 获取标签内文本
# 获取文本(分隔符、去除空白)
soup.get_text(separator=" ", strip=True)
  1. 根据CSS选择器选择标签
#获取a标签中具有href属性的标签
soup.select('a[href]')
  1. 正则匹配标签
# 选取所有的h标签,替换内容(去除h标签内的标签,只保留文本)
for i in soup.find_all(re.compile("^h[1-6]")):
	i.string = i.get_text()
  1. 删除标签
# 删除style中包含隐藏的标签
for i in self.soup.find_all(style=re.compile(r'.*display:none.*?')):
    i.decompose()
  1. 去除空标签
# 删除空白标签(例如<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文件可能需要较大的内存

文章作者: fdChen
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 fdChen !
评论
  目录
加载中...