Python爬虫

技术还是很弱,我并不知道该从何写起?因为学习过程中常常会推翻自己以前的结论(也就是打脸),我不能说现在想的一定是正确的,仅仅只能简述我现在的理解。如果有不恰当的地方,还望包容和指出,感谢

爬虫可以快速的自动获取数据,我觉得它主要可以分为两个部分:获取与分析



获取数据


有很多方法,可以通过urllib2库等,下面介绍两种简单的


1. requests库


(1)获取源码

import requests                                              # 引入requests库
HEAD = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54 Safari/537.36'}
result = requests.get('http://www.baidu.com', headers=HEAD)  # 获取百度首页,HEAD传入HTTP请求头
print result.content                                         # 打印源码

运行上面的代码,便可将百度的首页爬取下来。其中HEAD字典用于向服务器传递浏览器和操作系统的信息,服务器往往会检查这些信息来进行反爬虫,所以HTTP请求头需要携带HEAD字典。HEAD里的User-Agent值可以在右键“审查元素”中的“网络”选项里,选择任一文件,在“请求头”中获得。


(2)提交数据,模拟登陆杭电ACM网站

import requests
HEAD = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54 Safari/537.36'}
DATA = {'username': '你的用户名', 'userpass': '你的密码'}       # DATA字典存储需要提交的数据
requests.post('http://acm.hdu.edu.cn/userloginex.php?action=login', headers=HEAD, data=DATA)  # 向该地址提交数据,传入DATA
运行上面代码,即完成登录杭电,通过打印requests.content可知晓登录结果。其中post方法里的URL是form表单的action所指地址;如果action为空,则地址往往隐藏在JS代码里,需要仔细查找。DATA字典的key必须与请求头中的名称(如下图红圈)或是form表单对应标签的name属性一样。



但上面的代码并不是完美的,存在问题:

1)从返回的源码可以看出,其并没有执行JS。但有些数据可能是通过JS生成的,比如杭电ACM题目的URL就是JS生成的,链接

注:右键“查看源代码”是未执行JS的源码,右键“审查元素”中的“Elements”是执行JS后的源码,比较两者会发现它们往往不一样

2)有些数据是通过AJAX传回的,只有发生某些事件时(点击,下拉,...),数据才会源源不断加载进来,比如蚂蜂窝的行程页面, 链接(隔段时间后快速下拉,底部数据甚至还没加载);

总之,源码最好执行JS后再返回,并且能够模拟人类的下拉和点击行为,怎么办呢?

显然应该有个真实的浏览器,替我们将代码先执行一遍后再返回,并提供一些类似JS中dom定位的方法。那么,下面这款工具就很满足上述要求。


2. Selenium


Selenium是网页自动化测试的工具,相较于requests库,selenium自动对源码执行JS后再返回。其提供了各种浏览器的驱动,通过驱动调用对应浏览器(PhantomJS,Chrome,Firefox,...)。其中PhantomJS可以执行JS,但并不提供可视化界面,故相较于其他浏览器,PhantomJS更轻,更快。


< selenium文档:虫师英文 >


(1)获取源码

from selenium import webdriver
driver = webdriver.Chrome('./chromedriver')        # 调用Chrome驱动,打开浏览器
driver.get('http://www.baidu.com')                 # 跳转到该页面
print driver.page_source                           # 打印源码
driver.close()                                     # 关闭窗口,如果是PhantomJS则不需要
运行代码,即可打印出百度首页的源码;把它与requests库所打印的源码作比较,会发现其已经执行过JS,并且忽略了注释。

(2)提交数据,模拟登陆杭电ACM网站

from selenium import webdriver
driver = webdriver.Chrome('./chromedriver')
driver.get('http://acm.hdu.edu.cn/')
driver.find_elements_by_name('username')[0].send_keys('你的用户名')  # 定位元素,并写入值
driver.find_elements_by_name('userpass')[0].send_keys('你的密码')
driver.find_elements_by_name('login')[0].click()                   # 模拟点击form表单的提交按钮
driver.close()

使用了selenium驱动Chrome浏览器,结果是可视化的,可以看到登陆成功。

1)“driver.find_elements_by_name(text)”:通过name属性定位一系列HTML标签,其返回的是一个列表,通过下标可以选出符合的元素对象;

2)“元素对象.send_keys(text)”:向该元素写入文本text;

3)“元素对象.click()”:点击该元素,触发点击事件;

可以查找selenium的文档获得详细的解释和更多其他方法。


3. 比较requests和selenium模拟登录杭电ACM


1)两者均需要寻找form表单登录标签的name属性,但selenium提供了更多的方法,可以用其他属性来替代;

2)requests关键还要找到form表单提交到的URL,而selenium则不用关心这个,只需要模拟点击“提交”按钮;

3)requests的速度远快于selenium;

因此全部用selenium应该没问题,但不能说用selenium替代requests,因为selenium是驱动真实的浏览器,所以其所需资源要比requests多,因此能用requests就用requests。其次虽然selenium有很多驱动,但各驱动对同一语句的执行效果可能是不一样的,所以更换驱动不能简单只是更换“调用语句”。



解析数据


如果是考虑做搜索引擎,那么不需要获取数据,只要考虑如何剔除数据,比如剔除JS和HTML标签,把剩余的内容存储即可,这是一个做减法的过程。

如果是获取特定的数据,那么可以通过正则匹配,或是BeautifulSoup库来获取,下面使用BeautifulSoup库来打印杭电ACM每道题的题目,题目链接


< BeautifulSoup文档:链接 >


# coding=utf-8
from selenium import webdriver
from bs4 import BeautifulSoup


class Spider(object):                                          # 定义一个爬虫类

    driver = None

    def __init__(self):                                        # 起到构造函数的作用
        self.driver = webdriver.Chrome('./chromedriver')       # 调用并启动启动Chrome浏览器
        return

    def __del__(self):                                         # 起到析构函数的作用
        self.driver.close()
        return

    def get(self, num):                                        # 传入题目的id
        # 访问题目页,杭电ACM题目是按照pid从1001到2000+有序排列的
        self.driver.get('http://acm.hdu.edu.cn/showproblem.php?pid=%s' % num)
        soup = BeautifulSoup(self.driver.page_source, 'lxml')  # 通过源码得到文档对象
        # 判断是否存在h1标签,且style属性为color:#1A5CC8
        if soup.find('h1', style='color:#1A5CC8'):
            # 打印题目id和题目名称
            print num, soup.find('h1', style='color:#1A5CC8').get_text()
        return

if __name__ == '__main__':
    my = Spider()                   # 创建对象
    for i in range(1001, 1501):     # 迭代题目id
        my.get(i)                   # 调用get方法

1)“元素对象.get_text()”:获取该标签(包括子标签)的文字内容,剔除HTML代码,仅保留文本;

2)“BeautifulSoup(source, 'lxml')”:其中lxml为解释器类型,链接


结果:




更快的速度


当有大量数据需要爬取时,速度的瓶颈会显现出来,特别是使用selenium的时候,其取决于页面的加载时间,所以有时会很慢。

因此,为了加快爬取速度,准备引入进程。这里之所以用进程而不是线程,主要是考虑当一个进程异常后,不会影响到其他进程。线程之间会共用一些变量,一旦出错,可能会“带崩三路”。其次,Python中的线程只能利用单核,而进程却是可以利用多核资源的。

下面将上个案例(打印杭电ACM每道题的题目)改成多进程版本:
# coding=utf-8
import os
from selenium import webdriver
from bs4 import BeautifulSoup
from multiprocessing import Pool
my = [None for item in range(8)]  # 列表,存储爬虫对象,每个key对应一个独立的爬虫对象
PRO = 4                           # 进程数


class Spider(object):

    driver = None

    def __init__(self):
        self.driver = webdriver.Chrome('./chromedriver')  # 启动Chrome
        # self.driver=webdriver.PhantomJS('./phantomjs')  # 启动PhantomJS,无界面
        return

    def __del__(self):
        self.driver.close()
        return

    def get(self, num):                                   # 传入题目id
        self.driver.get('http://acm.hdu.edu.cn/showproblem.php?pid=%s' % num)  # 访问题目页
        soup = BeautifulSoup(self.driver.page_source, 'lxml')                  # 通过源码得到文档对象
        if soup.find('h1', style='color:#1A5CC8'):
            # 打印题目id和题目名称
            print num, soup.find('h1', style='color:#1A5CC8').get_text()
        return


def task(num):
    global my, PRO                      # 声明my与PRO是全局变量
    pid = int(os.getpid()) % PRO        # 根据进程号为其分配my[i],参考了循环队列
    try:
        my[pid].get(num)                # 调用
    except Exception, msg:
        print num, msg                  # 打印题目id与异常信息
        my[pid] = Spider()              # 为my[i]构建新的对象
        my[pid].get(num)                # 重新调用
    return

if __name__ == '__main__':
    for i in range(PRO):                # 为my[i]构建爬虫对象
        my[i] = Spider()
    p = Pool(PRO)                       # 进程池,传入容量
    for i in range(1001, 1501):         # 爬取题目id为1001~1500的题目名称
        p.apply_async(task, args=(i,))  # 异步,为每个题目id分配其的my[i],并执行
    p.close()
    p.join()
    print '*' * 18 + 'done' + '*' * 18

1)运行后,其速度较上个案例有了很大提升;本例中的进程数为计算机的核心数,如果进程数远超核心数,来回切换进程反而会降低爬取速度。

2)突破某速度,遭到封禁:
这样的情况自己还没有遇到,这可能也与我写的爬虫效率要求不是很高有关。我想如果使用selenium之后,对方服务器已经分不清坐在显示器那头的到底是人类还是爬虫。所以服务器可能只能通过速度来区别,并且一般网站应该不会把速度卡的很小,因为这有可能会误杀人类浏览者。当然如果遇到了封禁,可以通过控制速度或者使用代理来解决,这个我会继续研究的。



结尾


1. 爬虫与人类浏览者的区别很可能只在于浏览速度,只要不过某速度,就不会遭到封禁;

2. 爬虫不能生产数据,我们看到什么,它也只能获取到这些,仅仅是加快过程而已。



实验室


我制作了一个记录博客每天每篇文章访问量的爬虫,会自动更新文章与记录新博客,并提供备份功能。通过Python的Matplotlib库将爬取到的数据生成了几张图片,目前该项目还在持续推进。

如果你有兴趣,请戳这里;如果想查看目前的效果,点这里



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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值