网络爬虫基础一

爬虫的分类

按使用场景:

  1. 通用爬虫:指搜索引擎的爬虫
  2. 聚焦爬虫:指针对特定网站的爬虫
  • 聚焦爬虫又可以分为大致3种:
  1. 累积式爬虫: 从开始到结束,一直不断爬取,过程中会进行去重操作;

  2. 增量式爬虫: 对已经下载的网页采取增量式更新和只爬行新产生的或者已经发生变化网页的爬虫;

  3. 深度爬虫: 不能通过静态链接获取的、隐藏在搜索表单后的,只有用户提交一些关键词才能获得的Web面;

requests模块的使用

说明:Requests是一个用于网络请求的第三方模块,继承了urllib的所有特性,但是其API比urllib容易使用,更符合操作习惯;

GET请求:

import requests
response = requests.get("http://www.baidu.com/")
# 或者
response = requests.request("get", "http://www.baidu.com/")

参数:

  • headers:添加请求头,字典格式,默认的请求头为request库,容易被服务器反爬;
  • params:添加get参数;
  • cookies:添加cookies;
  • timeout:设置超时,默认180s,超时后报错;
import requests
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36"}
params = {'word':"huo"}
response = requests.get("https://www.so.com/", headers=headers,params =params,timeout=5)

POST请求:

import requests
response = requests.post("http://www.baidu.com/",data=data)
# 或者
response = requests.request("post", "http://www.baidu.com/",data=data)

参数:

  • data:post请求一般需要提交数据,字典格式;
  • 其他参数和get请求一样;

查看响应的内容:

response.url  # 查看url地址
response.status_code  # 查看响应状态码
response.encoding  # 查看相应头的字符编码
response.headers  # 查看相应头部
response.text  # 自动解码,返回unicode的自动解码的内容,有时会失败产生乱码
respones.content  # 返回二进制的内容
respones.json()  # 如果响应是json数据,可以使用
respones.cookies  # 获取cookie的信息,得到的是一个对象,需要装换格式

  • 注意:如果需要接收图片数据或不知道响应的编码格式,一般使用respones.content.decode("utf-8"),可以指定解码的格式;

常见的请求头的内容

  1. Host (主机和端口号)
  2. Connection (链接类型)
  3. Upgrade-Insecure-Requests (升级为HTTPS请求)
  4. User-Agent (浏览器名称)
  5. Accept (传输文件类型)
  6. Referer (页面跳转处)
  7. Accept-Encoding(文件编解码格式)
  8. Cookie (Cookie)

设置代理

  • 设置代理使用参数proxies;

  • 免费代理:不需要用户和密码,公开的,可去网上寻找

import requests
# 使用参数proxies
proxies = {
  "http": "http://host:port",  # http的访问方式
  "https": "http://host:port", # https的访问方式
}
response = requests.get("http://www.baidu.com", proxies = proxies)
  • 私密代理:需要用户和密码,需要购买
import requests
proxy = { "http": "用户名:密码@host:port" }
response = requests.get(url, proxies = proxy)

使用cookies和session

  • session:

session表示一个会话,登录一次后会自动记录cookie,验证一次后就可以绕过验证获取数据,用来获取那些需要登陆后才能获取的网页数据;

import requests
# 创建一个session会话
s = requests.session()
headers = {"User-Agent": "xxxxx"}
# 表单数据
data = {"user":"xxx", "password":"xxxxx"}  
s.post(url, data = data)  # 提交 表单,相当于登陆一个网站,后面就可以直接访问需要登录才能访问的url了
  • cookies:

cookie是保存在本地的服务器发送的信息,常常可以使用cookie跳过登陆验证的环节;

import requests
from requests.utils import dict_from_cookiejar
cook = response.cookies
cookdict = dict_from_cookiejar(respones.cookies)  # 将获取的内容转化成字典的格式

从response中提取数据

  • 使用post或get方法提交请求后,会返回response对象,其可能是一个json对象,也可能是html对象,下面讨论从html中提取数据。

  • 使用正则表达式匹配:不常用

  • csspath:常用,和css选取节点的方法一样,这里不多做介绍;

  • xpath:(XML language path),常用;

# 需要用到lxml第三方库
from lxml import etree
import requests
response = requests.get("http://www.baidu.com/")
# 将返回的数据转化成一个html对象
html = etree.HTML(response.content.decode())
# 使用xpath提取数据
result = html.xpath('//*')
print(result)

方法:

  1. 如果是将响应数据转化为html对象,使用html = etree.HTML(response));
  2. 如果是读取一个html格式的文件,使用html = etree.parse('xxx.html'))

保存数据

  • 保存为txt文档:一般的小文件使用
def save_text(data):
    with open("xxx.txt","w") as f:
        f.write(data)
  • 保存为json文件:在ajax加载上使用较多
import json
def save_json(data):
    with open("xxx.json","w") as f:
        # 使用dump将python的数据转换为json格式的数据
        # 参数 ensure_ascii=False 禁用ascii编码,按utf-8编码
        data = json.dump(data, ensure_ascii=False)
        f.write(data) 

更多json模块的方法,参考我的另一篇博文:python的json模块。

  • 使用redis、mongodb,mysql等数据库保存数据。

注意:

  • 很多时候需要知道网页的编码格式,如是utf-8或gbk等,编码和解码的格式必须一样,不然会产生乱码,这时可以用chardet模块查看编码。
import requests
import chardet
def req(url):
    # 设置请求头
    header ={
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
    }
    response = requests.get(url, headers=header, timeout=5)
    print(chardet.detect(response.content))  # 得到编码
    return response
if __name__ == "__main__":
    response = req("http://www.baidu.com")
    print(response.content.decode())

    # 结果:{'language': '', 'confidence': 0.99, 'encoding': 'utf-8'}

说明:detect方法返回一个字典,confidence表示判断的精度,即概率;encoding表示编码格式。

爬虫实例

说明:写一个完整的爬虫至少包括以下几个部分:

  1. 构建请求相关的参数,如headers,cookies,referer,proxes(代理)等,最重要的是初始的url;

  2. 请求数据,一般使用get,post或head(类似get方法,不过只获取头部信息);

  3. 解析数据:使用正则、jsonpath或xpath提取想要的数据和新的url

  4. 保存数据:将提取的数据保存到文件或数据库。

  • 一个单线程基础爬虫:

实现功能:爬百度贴吧(爱情吧),爬取内容:标题,回复人数,作者;

  1. 随机请求头;
  2. 有代理池;
  3. 可以爬取多页内容;
  4. url去重;
import requests as res
import sys
from queue import Queue
import random
from lxml import etree
import json

class Baidu_Tieba(object):
    def __init__(self, pn):
        # 构建请求头池
        self.headers = [
            {"User-Agent": "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)"},
            {"User-Agent": "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)"},
            {"User-Agent": "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0; by TSG)"},
            {"User-Agent": "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0; .NET CLR 1.0.3705)"},
        ]
        # 构建代理ip池
        self.proxies = [
            {'http': 'http://120.236.142.103:8888'},
            {'http': 'http://203.195.232.60:8000'},
            {'http': 'http://202.38.254.247:3128'},
            {'http': 'http://116.199.2.209:80'},
            {'http': 'http://111.62.251.130:8081'},
            {'http': 'http:116.199.115.78:80'},
            {'http': 'http://120.236.142.103:8888'}
        ]
        # 设置爬取多少页
        self.pn = pn
        # 设置初始url
        self.base_url = 'https://tieba.baidu.com/f?ie=utf-8&kw=爱情吧&fr=search'
        # 用来去重的集合
        self.url_set = set()
        # url请求队列,无所谓深度和广度优先,使用先进先出队列
        self.url_list = Queue()

    # 请求数据
    def get_page(self, url):
        header = random.choice(self.headers)
        proxies = random.choice(self.proxies)
        response = res.get(url, headers=header, proxies=proxies)
        return response.content

    # 解析数据
    def analysis_data(self, data):
        html = etree.HTML(data.decode())
        # 提取页面内容
        nodes = html.xpath('//*[@id="thread_list"]/li/div')
        data_list = []
        # 获取内容
        for node in nodes:
            item = {}
            item["标题"] = node.xpath('./div[2]/div[1]/div[1]/a/text()')
            item["评论"] = node.xpath('./div[1]/span/text()|./div[1]/div/text()')
            item['用户'] = node.xpath('./div[2]/div[1]/div[2]/span[1]/span[1]/a/text()')
            data_list.append(item)
            print(item)
        # 获取url
        url_nodes = html.xpath('//*[@id="frs_list_pager"]/a/@href')
        print(url_nodes)
        for url in url_nodes:
            url = 'https:' + url
            self.url_list.put(url)
        return data_list

    # 保存数据
    def save_data(self, data):
        filename = '爱情吧.json'
        with open(filename, "w", encoding='utf-8') as f:
            for d in data:
                da = json.dumps(d)
                f.write(da+',\n')

    def run(self):
        # 将初始url加入到url队列
        self.url_list.put(self.base_url)
        i = 0
        while i < self.pn:
            # 取出一条url
            url = self.url_list.get()
            # print(url)
            # 验证
            if url not in self.url_set:
                i += 1
                print(i)
                self.url_set.add(url)
                data = self.get_page(url)
                data_list = self.analysis_data(data)
                self.save_data(data_list)

if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("参数不足,如'python 百度贴吧爬虫.py 3'")
    else:
        np = sys.argv[1]
        tieba = Baidu_Tieba(int(np))
        tieba.run()
  • 注意

百度贴吧的html对以Chrome为内核的浏览器发送的是被注释的源码,所以xpath得不到相关的内容,必须设置User-Agent是ie内核的。

  • 说明:

上述爬虫是最基础的单线程爬虫,其效率和抗风险的能力是很差的,如:

  1. 当代理服务器失效,或无法访问的时候,会阻塞爬虫的运行或超时错误;
  2. 当百度贴吧的服务器没有响应的时候,程序阻塞;
  3. 当爬取的页面过多时,可能会陷入死循环;

为了提高爬虫的效率和抗风险的能力,需要进一步学习爬虫知识,下一篇博文将继续介绍。

  • 作者:天宇之游
  • 出处:http://www.cnblogs.com/cwp-bg/
  • 本文版权归作者和博客园共有,欢迎转载、交流,但未经作者同意必须保留此段声明,且在文章明显位置给出原文链接。
posted @ 2018-01-03 09:50  倥偬时光  阅读(1517)  评论(2编辑  收藏  举报