学习笔记-爬虫-简单的网页爬虫开发

内容是对《Python爬虫开发:从入门到实战》的摘录、理解、代码实践和遇到的问题。

全自动爬虫

上一个阶段案例中,实现了半自动爬虫开发,要实现“全自动“的爬虫,还需要把其中“手动复制粘贴网页源代码”的部分自动化。可以通过requests库实现这一点。

requests是Python的一个第三方HTTP(Hypertext Transfer Protocol 超文本传输协议)库,它比Python自带的网络库urlib更简单、方便和人性化。使用requests可以让Python实现访问网页并获取源码的功能。

requests是第三方库,需要手动安装,手动安装过程省略。

使用requests获取网页源代码

网页有很多种打开方式,最常见的是GET方式和POST方式。在浏览器中可以通过直接输入网址访问的页面就是使用了GET方式;只能通过从另一个页面单击某个链接或某个按钮以后转跳,而不能通过输入网址访问的网页就是使用了POST方式

有一些网页,使用GET和POST方式访问相同的网址得到的结果不一样;还有一些网页只能使用POST方式访问,使用GET方式访问会直接返回错误信息。

GET方式

对于使用GET方式的网页,在Python里可以使用requests的get()方法获取网页的源代码。

import requests
html = requests.get(' https://tieba.baidu.com/p/8466216461')
#   此时直接打印html得到的是“<Response [200]>”,无法得到网页源码
html_bytes = html.content   # 使用.content属性来显示bytes型的网页源码
html_str = html_bytes.decode()      # 将bytes型的网页源代码解码为字符串型的源代码,因为在bytes型的数据类型下,中文是无法显示的

以上可以缩写为:

import requests

html = requests.get('https://tieba.baidu.com/p/8466216461').content.decode()

.decode()的参数可以省略,省略时默认使用UTF-8编码格式来把bytes型解码为字符串型的源码。对于编码格式本身不是UTF-8的页面,就需要在括号里写目标编码格式的名字,如:.decode(‘GBK’)等。

编码格式有几十种,但最常见的是‘UTF-8’,’GBK’,’GB2312’和’GB18030’。不确定编码格式的时候可以每种都尝试一下,打印源代码后中文正常显示就行。

POST方式

使用requests的post()方法

import requests

data = {

    'key1': 'value1',

    'key2': 'value2'

}

html_formdata = requests.post('网址', data=data).content.decode()

#   使用formdata提交数据

其中,data这个字典的内容和项数需要根据实际情况修改,Key和Value在不同的网站是不一样的,构造这个字典是做爬虫的任务之一。

还有一些网站,提交的内容需要时JSON格式的,因此post()方法的参数需要进行一些修改

html_formdata = requests.post('网址', json=data).content.decode()

#   使用json提交数据

这样requests就可以自动将字典转换为JSON字符串。

(关于data字典的构造方式还不太清楚,所以这个部分没有获取实际网页源码的例子)

结合requests对“半自动爬虫”改进

import json

import re

import csv

import requests



'''

“半自动爬虫”的源码获取方法

#   读入保存在code.txt中的网页源码

with open('code.txt', encoding='UTF-8') as f:

    source = f.read()

'''

#   使用requests获取源码

source = requests.get('https://tieba.baidu.com/p/8537125946').content.decode()



#   根据源码与网页内容的比对,找出每一层回复开始、结尾的规律,获得按层划分的大文本块

every_reply = re.findall(

    '<li class="d_nameplate">(.*?)<ul class="p_props_tail props_appraise_wrap">',

    source, re.S)



#   将分层写入txt文件,便于观察改进

with open('separated.txt', 'w', encoding='UTF-8') as f:

    for eachRow in every_reply:

        f.write(eachRow)

        for i in range(7):

            f.write('\n')



#   从每一个大文本块里提取出该层回复的回复者、回复内容和回复时间,保存在result_list中

result_list = []

for each in every_reply:

    result = {}

    result['userName'] = re.findall('&fr=pb" target="_blank">(.*?)</a>', each, re.S)

    result['content'] = re.findall('class="d_post_content j_d_post_content " style="display:;">(.*?)</div><br>', each,

                                   re.S)

    result['reply_time'] = re.findall('<span class="tail-info">(2023.*?)</span><div', each, re.S)

    result_list.append(result)



#   将result_list写入csv文件

with open('file.csv', 'w', encoding='UTF-8-sig', newline="") as f:

    writer = csv.DictWriter(f, fieldnames=['userName', 'content', 'reply_time'])

    writer.writeheader()

    writer.writerows(result_list)
 

 

多线程爬虫

上面实现的爬虫只有一个进程、一个线程,因此称为单线程爬虫。单线程爬虫每次只访问一个页面,而一个页面最多也就几百KB,不能充分利用计算机的网络带宽,多出来的网速和从发起请求到得到源码中间的时间都被浪费了。如果可以同时让爬虫访问10个页面,就相当于爬取速度提高了10倍。为了达到这个目的,就需要使用多线程技术。

一点补充:

Python这门语言在设计的时候,有一个全局解释器锁(Global Interpreter Lock, GIL),这导致Python的多线程都是伪多线程,即本质上还是一个线程,但这个线程每件事只做几毫秒,几毫秒后就保存现场,换做其他事情…微观上的单线程,在宏观上就像同时在做几件事。这种机制在I/O密集型的操作上影响不大,但在计算密集型的操作上会对性能产生非常大的影响,所以涉及计算密集型的程序,需要使用多进程,Python的多进程不受GIL的影响。

爬虫属于I/O密集型的程序,所以使用多线程可以大大提高爬取效率。

多进程库multiprocessing

multiprocessing是Python的多进程库,用来处理与多进程相关的操作。但是由于进程与进程之间不能直接共享内存和堆栈资源,而且启动新的进程开销也比线程大得多,因此使用多线程来爬取比使用多进程有更多的优势。multiprocessing下面有一个dummy模块,它可以让Python的线程使用multiprocessing的各种方法。

dummy下有一个Pool类,它用来实现线程池。这个线程池有一个map方法,可以让线程池里面所有线程都“同时”执行一个函数。

开发多线程爬虫

多线程操作与异步操作

本小节讲到的是多线程操作,后面的章节会讲到异步操作的爬虫框架。在需要操作的动作数量不大时,这两种方式的性能没什么区别,但当动作的数量大量增大,多线程的效率提升就会下降,甚至比单线程还差,这种情况下就需要采用异步操作。

比较单线程爬虫和多线程爬虫爬取百度首页的性能差异

使用单线程循环访问百度首页100次并计算时间:

import json

import re

import csv

import time

import requests



stat = time.time()

for i in range(100):

    requests.get('https://www.baidu.com/')

end = time.time()

print(f'单线程循环访问100次百度首页,耗时为:{end-stat}')

单线程循环访问100次百度首页,耗时为:32.5662727355957

使用五个线程访问百度首页100次并计算时间:

import json

import re

import csv

import time

import multiprocessing



import requests





def query(url):

    requests.get(url)





if __name__ == '__main__':

    stat = time.time()

    url_list = []

    for i in range(100):

        url_list.append('https://www.baidu.com/')

    pool = multiprocessing.Pool(5)

    pool.map(query, url_list)

    #   线程池的map()方法接收两个参数,第一个参数是所有线程都要执行的函数的函数名(仅仅是函数名字,不带括号),第二个参数是一个列表,是将要用于前面函数的参数们

    end = time.time()

    print(f'使用5个线程访问100次百度首页,耗时:{end - stat}')

使用5个线程访问100次百度首页,耗时:10.967380285263062

另外:

需要注意的是,在Windows上要想使用进程模块,就必须把有关进程的代码写在if __name__ == ‘__main__’ 内,否则在Windows下使用进程模块会产生异常。Unix/Linux下则不需要。

python 进程池multiprocessing.Pool(44) - 知乎 (zhihu.com)

可以看出5个线程同时运行确实比单线程效率高。

但需要注意,并非线程池设置得越大越好。从上面的结果也可以看出,5个线程的运行时间实际是大于单线程运行时间五分之一的,多出来的其实就是线程切换的时间,这从侧面反映了Python的多线程在微观上还是串行的。因此,如果线程池设置得过大,线程切换导致的开销可能会抵消多线程带来的性能提升。

爬虫常见的搜索算法

深度优先搜索和广度优先搜索。

深度优先搜索:把一个大类的所有信息都爬取完后再爬取下一个大类的所有信息。

广度优先搜索:先爬取完所有大类,再进一步爬取大类下的信息。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值