《精通python网络爬虫》笔记

时间:2022-09-06 20:44:35

《精通python网络爬虫》韦玮 著

目录结构

第一章 什么是网络爬虫

第二章 爬虫技能概览

第三章 爬虫实现原理与实现技术

第四章 Urllib库与URLError异常处理

第五章 正则表达式与Cookie使用

第六章 手写Python爬虫

第七章 学会使用 Fiddler

第八章 爬虫的浏览器伪装技术

第九章 爬虫的定向爬取技术

第十章 了解Python爬虫框架

第十一章 爬虫利器----Scrapy安装与配置

第十二章 开启Scrapy爬虫项目之旅

第十三章 Scrapy核心架构

第十四章 Scrapy 中文输出与存储

第十五章 编写自动爬取网页的爬虫

第十六章 CrawlSpider

第十七章 Scrapy高级应用

第十八章 博客类爬虫项目

第十九章 图片类爬虫项目

第二十章 模拟登陆爬虫项目

Urllib库的使用

在Python2.x中有Urllib库也有Urllib2库,在Python3.x中Urllib2合并到Urllib中。本书使用的是python3.5.2

爬取网页

import urllib.request

file = urllib.request.urlopen("http://www.cnblogs.com/0bug/")
data = file.read()  # 读取全部内容赋给一个字符串变量
# datalines = file.readlines()  # 与read不同的是把读取到的内容赋值给列表变量
# dataline = file.readline()  # 读取文件的一行内容
print(data)

将爬取的内容以网页的形式保存到本地

import urllib.request

file = urllib.request.urlopen("http://www.cnblogs.com/0bug/")
data = file.read()
with open(r'D:\1.html', "wb") as f:
    f.write(data)

除此之外,还可以使用urllib.request里面的urlretrieve()函数直接将对应信息写入本地

import urllib.request

file = urllib.request.urlretrieve("http://www.cnblogs.com/0bug/", filename=r"D:\2.html")
urllib.request.urlcleanup()  # 清除缓存

将url编码

一般来说,url标准中只允许一部分的ASCII字符比如数字、字母、部分符号等,而其他的比如汉子不符合标准需要进行编码

import urllib.request

r = urllib.request.quote('http://www.cnblogs.com/0bug/')  # 编码
print(r)  # http%3A//www.cnblogs.com/0bug/
r2 = urllib.request.unquote('http%3A//www.cnblogs.com/0bug/')  # 解码
print(r2)  # http://www.cnblogs.com/0bug/

添加报头两种方式

方式一

import urllib.request

url = 'http://www.cnblogs.com/0bug/'
headers = ("User-Agent",
           "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
opener = urllib.request.build_opener()
opener.addheaders = [headers]
data = opener.open(url).read()
with open(r'D:\3.html', "wb") as f:
    f.write(data)

方式二

import urllib.request

url = 'http://www.cnblogs.com/0bug/'
req = urllib.request.Request(url)
req.add_header("User-Agent",
               "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
data = urllib.request.urlopen(req).read()

with open(r'D:\4.html', "wb") as f:
    f.write(data)

超时设置

import urllib.request

url = 'http://www.cnblogs.com/0bug/'
for i in range(100):
    try:
        file = urllib.request.urlopen(url, timeout=0.6) # 单位是秒
        data = file.read()
        print(len(data))
    except Exception as e:
        print('出现异常-->' + str(e))

构造GET请求

(注意:需要通过urllib.request.quote()对URL进行编码处理)

import urllib.request

keywd = '春江花月夜'
keywd=urllib.request.quote(keywd)
url = "https://www.baidu.com/s?wd=" + keywd
req = urllib.request.Request(url)
req.add_header("User-Agent",
               "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
data = urllib.request.urlopen(req).read()
with open(r'D:\5.html', "wb") as f:
    f.write(data)

构造POST请求

不考虑Cookie,以作者提供的这个网站为例http://www.iqianyue.com/mypost

import urllib.request
import urllib.parse

url = 'http://www.iqianyue.com/mypost'
postdata = urllib.parse.urlencode({
    'name': 'lichengguang',
    'pass': 'lcg*^_8'
}).encode('utf-8')  # 将数据使用urlencode编码处理后,使用encode设置为utf-8编码
req = urllib.request.Request(url, postdata)
req.add_header("User-Agent",
               "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
data = urllib.request.urlopen(req).read()
with open(r'D:\6.html', "wb") as f:
    f.write(data)

代理服务的设置

import urllib.request

def use_proxy(proxy_addr, url):
    """
    该函数实现使用代理服务器来爬取某个URL网页的功能
    :param proxy_addr: 代理服务器的地址
    :param url: 爬取的网页的地址
    :return: 爬取的网页内容
    """
    proxy = urllib.request.ProxyHandler({'http': proxy_addr})
    opener = urllib.request.build_opener(proxy, urllib.request.HTTPHandler)
    urllib.request.install_opener(opener)  # 创建全局默认的opener对象,那么使用urlopen()时亦会使用我们安装的opener对象
    data = urllib.request.urlopen(url).read().decode("utf-8")
    return data

proxy_addr = '114.228.210.103:8118'  # 代理地址取自:http://www.xicidaili.com/
data = use_proxy(proxy_addr, 'http://www.cnblogs.com/0bug/')
print(len(data))  # 如果proxy_addr失效,则会抛出异常

开启DebugLog

有时我们希望边运行程序边打印调试日志,此时就需要开启DebugLog。

开启步骤:

  1、分别使用 urllib. request Httphandler(和 urllib. request. Httpshandlero将 debuglevel设置为1

  2、使用 urllib request build opener0创建自定义的 opener对象,并使用1)中设置的值作为参数。

  3、用 urllib request install opener0创建全局默认的 opener对象,这样,在使用urlopeno时,也会使用我们安装的 opener对象。

  4、进行后续相应的操作,比如 urlopeno等。此时,根据以上思路,我们可以通过如下代码开启 Debug

import urllib.request

httphd = urllib.request.HTTPHandler(debuglevel=1)
httpshd = urllib.request.HTTPSHandler(debuglevel=1)
opener = urllib.request.build_opener(httphd, httpshd)
urllib.request.install_opener(opener)
data = urllib.request.urlopen("http://edu.51cto.com")
print(data)

异常处理神器-URLError

URLError:

import urllib.request
import urllib.error

"""
    URLError原因:
        1.连接不上服务器
        2.远程URL不存在
        3.无网络
        4.触发了HTTPError
"""
try:
    data = urllib.request.urlopen("http://www.adafsdagrfesda.com")
    print(data.read())
except urllib.error.URLError as e:
    print(e.reason)

HTTPError

HTTPError为URLError的子类,这种方法只能处理以上四中情况中的第四种情况

import urllib.request
import urllib.error
try:
     data = urllib.request.urlopen("http://www.fsadwafddss.com")
     print(data.encode)
 except urllib.error.HTTPError as e:
     print(e.code)
     print(e.reason)

如果我们需要在抛出HTTPError异常的时候得到e.code就不能用方法一里的URLError那段代码代替HTTPError,但是可以做一下改进。使用hasattr函数判断是否有code属性。

代码改进如下:

import urllib.request

import urllib.error

try:
    data = urllib.request.urlopen('http://wwwmm.cnblogs.com/0bug/').read()
    print(data)
except urllib.error.URLError as e:
    if hasattr(e, "code"):
        print(e.code)
    if hasattr(e, "reason"):
        print(e.reason)

# 404
# Not Found

常见异常码:

200 OK
        一切正常

    300 Moved Permanently
        重定向到新的URL,永久性

    302 Found
        重定向到临时的URL,非永久性

    304 Not Modified
        请求的资源未更新

    400 Bad Request
        非法请求

    401 Unauthorized
        请求未经授权

    403 Forbidden
        禁止访问

    404 Not Found
        没有找到对应的页面

    500 Internal Server Error
        服务器内部出现错误

    501 Not Implemented
        服务器不支持实现请求所需的功能

正则表达式

这里有过总结:http://www.cnblogs.com/0bug/p/8262662.html

  • 普通字符作为原子(数字,大小写字母,下划线)
import re

pattern = '0bug'
str_url = 'http://www.cnblogs.com/0bug/'
r = re.search(pattern, str_url)
print(type(r))  # <class '_sre.SRE_Match'>
print(r)  # <_sre.SRE_Match object; span=(23, 27), match='0bug'>
print(r[0])  # 0bug
print(r.group())  # 0bug
  • 非打印字符作为原子(指的是在字符串中用于格式控制的字符,如换行符等)

\n :用于匹配一个换号符

\t :用于匹配一个制表符

import re

pattern = '\n'
string = '''aaaaa
bbbbbb'''
result = re.search(pattern, string)
print(result)
# <_sre.SRE_Match object; span=(5, 6), match='\n'>
  • 通用子符作为原子(一个原子可以匹配一类字符)
\w:匹配任意数字、字母或下划线
\W:匹配除数字、字母或下划线以外的任意一个字符
\d:匹配任意一个十进制数
\D:匹配除十进制数以外的任意一其他字符
\s:匹配任意一个空白字符
\S:匹配除空白字符以外的任意一其他字符

例子:

import re

pattern = '\w\dpython\w'
string = 'abcdf233pythonadad_'
result = re.search(pattern, string)
print(result)
# <_sre.SRE_Match object; span=(6, 15), match='33pythona'>
  • 原子表 []

原子表可以定义一组地位平等的原子,然后匹配 的时候回取该原子表中的任意一个原子进行匹配。

import re

pattern = 'a[bcd]'
string = 'abdsacdaadfsfaaae_'
result = re.compile(pattern).findall(string)
print(result)
# ['ab', 'ac', 'ad']
  • 原字符(正则表达式中具有一些特殊含义的字符)
.:     匹配除换行符以外的任意字符
^:     匹配字符串的开始位置
$:     匹配字符串的结束位置
*:     匹配0次1次或多次前面的原子
?:     匹配0次或1次前面的原子
+:     匹配1次或多次前面的原子
{n}:   前面的原子恰巧出现n次
{n,}:  前面的原子至少出现n次
{n,m}: 前面的原子至少出现n次,最多出现m次
|:     模式选择符
():    模式单元符

模式选择符:|

使用模式选择符可以设置多个模式,匹配时,可以从中选择任意一个模式匹配,

import re

pattern = 'ab\w|cd\w'
string = 'abdsacdaabfsfaaae_'
result = re.compile(pattern).findall(string)
print(result)
# ['abd', 'cda', 'abf']

模式单元符()

可以使用模式单元符“()”将一些原子组合一个大原子使用,小括号括起来的部分会被当做一个整体去使用。

import re

pattern1 = '(as){2}'
pattern2 = '(ab){2}'
string = 'asababdsfdcdab'
result1 = re.compile(pattern1).findall(string)
result2 = re.compile(pattern2).findall(string)
print(result1)  # []
print(result2)  # ['ab']

模式修正符

所谓模式修正符,即在不改变正则表达式的情况下,通过模式修正符改变正则表达式的含义,从而实现一些匹配结果的调整等功能。

I:匹配时忽略大小写
M:多行匹配
L:做本地化识别匹配
U:根据Unicode字符及解析字符
S:让.匹配包括换行符,即用了该模式修正符后,'.'匹配就可以匹配任意的字符了
  • 贪婪模式与懒惰模式

总的来说,贪婪模式就行尽可能多的匹配,相反地,懒惰匹配是尽可能少的匹配。

import re

pattern1 = 'a.*b'  # 贪婪匹配
pattern2 = 'a.*?b'  # 懒惰匹配
string = 'asababdsfdcdab'
result1 = re.compile(pattern1).findall(string)
result2 = re.compile(pattern2).findall(string)
print(result1)  # ['asababdsfdcdab']
print(result2)  # ['asab', 'ab', 'ab']
  • re模块常见函数

re.match()函数、re.search()函数、全局匹配函数、re.sub()函数。

re.match()函数

re.match(pattern, string, flags=0)
功能:从源字符串的起始匹配
参数说明:
    - pattern: 正则表达式
    - string:  对应的源字符
    - flags:   可选,代表对应的标志位,可以放模式修正符

re.search()函数

功能:与re.match()函数不同的是re.search()函数会扫描整个字符串并进行对应的匹配。

全局匹配函数

re.match()函数与re.search()函数匹配的结果中,即便是源字符串中有多个结果符合模式,也只会匹配到一个结果,全局匹配可以匹配到所有符合匹配模式的结果。

思路:

     1. 使用re.complie()对正则表达式进行预编译。
   2. 编译后,使用findall()根据正则表达式从源字符中将匹配的结果全部找出。

以下实现以便理解:

import re

string = 'asdaabcsdabcdssabccab'
pattern = re.compile(".abc.")
result = pattern.findall(string)
print(result)  # ['aabcs', 'dabcd', 'sabcc']

上面的实例可以改写成:

import re

string = 'asdaabcsdabcdssabccab'
pattern = ".abc."
result = re.compile(pattern).findall(string)
print(result)  # ['aabcs', 'dabcd', 'sabcc']

re.sub函数

sub(pattern, repl, string, count=0, flags=0)
功能:实现替换匹配到的字符串
参数说明:
    - pattern:正则表达式
    - repl:   要替换成的字符串
    - string   源字符串
    - count    可选项,代表最多替换次数,如果忽略不写,会将符合模式的结果全部替换
    - flags    可选,代表对应的标志位,可以放模式修正符

例子:

import re

string = 'asdaabcsdabcdssabccab'
pattern1 = ".abc."
pattern2 = "s"
result1 = re.sub(pattern1, "xxx", string)
result2 = re.sub(pattern2, "6", string, count=2)
print(result1)  # asdxxxxxxsxxxab
print(result2)  # a6daabc6dabcdssabccab

Cookiejar实战精析(无csrf_coken)

import urllib.request
import urllib.parse
import http.cookiejar
import re

url = 'http://bbs.chinaunix.net/member.php?mod=logging&action=login&loginsubmit=yes&loginhash=LiZ3D'

postdata = urllib.parse.urlencode({
    'username': 'weisuen',
    'password': 'aA123456'
}).encode('utf-8')
req = urllib.request.Request(url, postdata)
req.add_header("User-Agent",
               "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
# 使用http.cookiejar.CookieJar()创建CookieJar对象
cjar = http.cookiejar.CookieJar()
# 使用HTTPCookieProcessor创建cookie,并以其为参数构建opener对象
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cjar))
# 将opener安装为全局
urllib.request.install_opener(opener)
file = opener.open(req)
data = file.read()
with open(r'D:\7.html', "wb") as f1:
    f1.write(data)
url2 = 'http://bbs.chinaunix.net/'
data2 = urllib.request.urlopen(url2).read()
with open(r'D:\8.html', "wb") as f2:
    f2.write(data2)

图片爬虫实战

爬取京东手机图https://list.jd.com/list.html?cat=9987,653,655&page=1

"""
图片1对应代码:
<img width="220" height="220" data-img="1" data-lazy-img="//img12.360buyimg.com/n7/jfs/t2437/118/775474476/74776/4087862f/562616d9Nc17cd80a.jpg">
图片2对应代码:
<img width="220" height="220" data-img="1" data-lazy-img="//img10.360buyimg.com/n7/jfs/t2230/83/2893465811/162158/80a547ef/56fa0f30N7794db4a.jpg">

"""
import re
import urllib.request
import urllib.error

def craw(url, page):
    html1 = urllib.request.urlopen(url).read()
    html1 = str(html1)
    pat1 = '<div id="plist".+? <div class="page clearfix">'
    result1 = re.compile(pat1).findall(html1)
    result1 = result1[0]
    pat2 = '<img width="220" height="220" data-img="1" data-lazy-img="//(.+?\.jpg)">'
    imagelist = re.compile(pat2).findall(result1)  # 将不需要的过滤掉
    x = 1
    for imageurl in imagelist:
        imagename = "D:/" + str(page) + str(x) + ".jpg"  # 拼接图片路径和图片名
        imageurl = "http://" + imageurl
        try:
            urllib.request.urlretrieve(imageurl, filename=imagename)
        except urllib.error.URLError as e:
            if hasattr(e, "code"):
                x += 1
            if hasattr(e, "reason"):
                x += 1
        x += 1

for i in range(1, 30):
    url = 'https://list.jd.com/list.html?cat=9987,653,655&page=' + str(i)  # 每一页的url
    craw(url, i)

链接爬虫实战

import re
import urllib.request
def getlink(url):
    #模拟成浏览器
    headers=("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 Safari/537.36 SE 2.X MetaSr 1.0")
    opener = urllib.request.build_opener()
    opener.addheaders = [headers]
    #将opener安装为全局
    urllib.request.install_opener(opener)
    file=urllib.request.urlopen(url)
    data=str(file.read())
    #根据需求构建好链接表达式
    pat='(https?://[^\s)";]+\.(\w|/)*)'
    link=re.compile(pat).findall(data)
    #去除重复元素
    link=list(set(link))
    return link
#要爬取的网页链接
url="http://blog.****.net/"
#获取对应网页中包含的链接地址
linklist=getlink(url)
#通过for循环分别遍历输出获取到的链接地址到屏幕上
for link in linklist:
    print(link[0])

糗事百科爬虫实战

import urllib.request
import re

def getcontent(url, page):
    # 模拟成浏览器
    headers = ("User-Agent",
               "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 Safari/537.36 SE 2.X MetaSr 1.0")
    opener = urllib.request.build_opener()
    opener.addheaders = [headers]
    # 将opener安装为全局
    urllib.request.install_opener(opener)
    data = urllib.request.urlopen(url).read().decode("utf-8")
    # 构建对应用户提取的正则表达式
    userpat = 'target="_blank" title="(.*?)">'
    # 构建段子内容提取的正则表达式
    contentpat = '<div class="content">(.*?)</div>'
    # 寻找出所有的用户
    userlist = re.compile(userpat, re.S).findall(data)
    # 寻找出所有的内容
    contentlist = re.compile(contentpat, re.S).findall(data)
    x = 1
    # 通过for循环遍历段子内容并将内容分别赋给对应的变量
    for content in contentlist:
        content = content.replace("\n", "")
        # 用字符串作为变量名,先将对应字符串赋给一个变量
        name = "content" + str(x)
        # 通过exec()函数实现用字符串作为变量名并赋值
        exec(name + '=content')
        x += 1
    y = 1
    # 通过for循环遍历用户,并输出该用户对应的内容
    for user in userlist:
        name = "content" + str(y)
        print("用户" + str(page) + str(y) + "是:" + user)
        print("内容是:")
        exec("print(" + name + ")")
        print("\n")
        y += 1

# 分别获取各页的段子,通过for循环可以获取多页
for i in range(1, 30):
    url = "http://www.qiushibaike.com/8hr/page/" + str(i)
    getcontent(url, i)

微信爬虫实战

爬取:http://weixin.sogou.com/

搜索物联,根据分析以下链接是有用的:

http://weixin.sogou.com/weixin?query=%E7%89%A9%E8%81%94&type=2&page=9

type控制搜索类型,query对应关键字,page对应页码,我们可以应用前面所学的解码规则解码看一下:

import urllib.request

r2 = urllib.request.unquote('http://weixin.sogou.com/weixin?query=%E7%89%A9%E8%81%94&type=2&page=9')  # 解码
print(r2)  # http://weixin.sogou.com/weixin?query=物联&type=2&page=9

项目规划:

建立三个自定义函数,一个函数实现使用代理服务器爬取指定网站并返回爬取到的数据的功能,一个函数实现获取多个页面的所有文章链接的功能,另一个函数实现根据文章链接爬取指定标题和内容并写入文件中的功能。需要注意的是,需要对可能会发生异常的地方做异常处理和延迟处理,对关键字使用urllib.request.quote(key)进行编码,完整代码如下

示例代码:

import re
import urllib.request
import time
import urllib.error

# 模拟成浏览器
headers = ("User-Agent",
           "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 Safari/537.36 SE 2.X MetaSr 1.0")
opener = urllib.request.build_opener()
opener.addheaders = [headers]
# 将opener安装为全局
urllib.request.install_opener(opener)
# 设置一个列表listurl存储文章网址列表
listurl = []

# 自定义函数,功能为使用代理服务器
def use_proxy(proxy_addr, url):
    # 建立异常处理机制
    try:
        import urllib.request
        proxy = urllib.request.ProxyHandler({'http': proxy_addr})
        opener = urllib.request.build_opener(proxy, urllib.request.HTTPHandler)
        urllib.request.install_opener(opener)
        data = urllib.request.urlopen(url).read().decode('utf-8')
        return data
    except urllib.error.URLError as e:
        if hasattr(e, "code"):
            print(e.code)
        if hasattr(e, "reason"):
            print(e.reason)
        # 若为URLError异常,延时10秒执行
        time.sleep(10)
    except Exception as e:
        print("exception:" + str(e))
        # 若为Exception异常,延时1秒执行
        time.sleep(1)

# 获取所有文章链接
def getlisturl(key, pagestart, pageend, proxy):
    try:
        page = pagestart
        # 编码关键词key
        keycode = urllib.request.quote(key)
        # 编码"&page"
        pagecode = urllib.request.quote("&page")
        # 循环爬取各页的文章链接
        for page in range(pagestart, pageend + 1):
            # 分别构建各页的url链接,每次循环构建一次
            url = "http://weixin.sogou.com/weixin?type=2&query=" + keycode + pagecode + str(page)
            # 用代理服务器爬,解决IP被封杀问题
            data1 = use_proxy(proxy, url)
            # 获取文章链接的正则表达式
            listurlpat = '<div class="txt-box">.*?(http://.*?)"'
            # 获取每页的所有文章链接并添加到列表listurl中
            listurl.append(re.compile(listurlpat, re.S).findall(data1))
        print("共获取到" + str(len(listurl)) + "页")  # 便于调试
        return listurl
    except urllib.error.URLError as e:
        if hasattr(e, "code"):
            print(e.code)
        if hasattr(e, "reason"):
            print(e.reason)
        # 若为URLError异常,延时10秒执行
        time.sleep(10)
    except Exception as e:
        print("exception:" + str(e))
        # 若为Exception异常,延时1秒执行
        time.sleep(1)

# 通过文章链接获取对应内容
def getcontent(listurl, proxy):
    i = 0
    # 设置本地文件中的开始html编码
    html1 = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>微信文章页面</title>
    </head>
    <body>'''
    fh = open("D:/9.html", "wb")
    fh.write(html1.encode("utf-8"))
    fh.close()
    # 再次以追加写入的方式打开文件,以写入对应文章内容
    fh = open("D:/Python35/myweb/part6/1.html", "ab")
    # 此时listurl为二维列表,形如listurl[][],第一维存储的信息跟第几页相关,第二维存的跟该页第几个文章链接相关
    for i in range(0, len(listurl)):
        for j in range(0, len(listurl[i])):
            try:
                url = listurl[i][j]
                # 处理成真实url,读者亦可以观察对应网址的关系自行分析,采集网址比真实网址多了一串amp
                url = url.replace("amp;", "")
                # 使用代理去爬取对应网址的内容
                data = use_proxy(proxy, url)
                # 文章标题正则表达式
                titlepat = "<title>(.*?)</title>"
                # 文章内容正则表达式
                contentpat = 'id="js_content">(.*?)id="js_sg_bar"'
                # 通过对应正则表达式找到标题并赋给列表title
                title = re.compile(titlepat).findall(data)
                # 通过对应正则表达式找到内容并赋给列表content
                content = re.compile(contentpat, re.S).findall(data)
                # 初始化标题与内容
                thistitle = "此次没有获取到"
                thiscontent = "此次没有获取到"
                # 如果标题列表不为空,说明找到了标题,取列表第零个元素,即此次标题赋给变量thistitle
                if (title != []):
                    thistitle = title[0]
                if (content != []):
                    thiscontent = content[0]
                # 将标题与内容汇总赋给变量dataall
                dataall = "<p>标题为:" + thistitle + "</p><p>内容为:" + thiscontent + "</p><br>"
                # 将该篇文章的标题与内容的总信息写入对应文件
                fh.write(dataall.encode("utf-8"))
                print("第" + str(i) + "个网页第" + str(j) + "次处理")  # 便于调试
            except urllib.error.URLError as e:
                if hasattr(e, "code"):
                    print(e.code)
                if hasattr(e, "reason"):
                    print(e.reason)
                # 若为URLError异常,延时10秒执行
                time.sleep(10)
            except Exception as e:
                print("exception:" + str(e))
                # 若为Exception异常,延时1秒执行
                time.sleep(1)
    fh.close()
    # 设置并写入本地文件的html后面结束部分代码
    html2 = '''</body>
    </html>
    '''
    fh = open("D:/9.html", "ab")
    fh.write(html2.encode("utf-8"))
    fh.close()

# 设置关键词
key = "物联网"
# 设置代理服务器,该代理服务器有可能失效,读者需要换成新的有效代理服务器
proxy = "119.6.136.122:80"
# 可以为getlisturl()与getcontent()设置不同的代理服务器,此处没有启用该项设置
proxy2 = ""
# 起始页
pagestart = 1
# 抓取到哪页
pageend = 2
listurl = getlisturl(key, pagestart, pageend, proxy)
getcontent(listurl, proxy)

多线程爬虫实战

多线程基础

import threading

class A(threading.Thread):
    def __init__(self):
        # 初始化该线程
        threading.Thread.__init__(self)

    def run(self):
        # 该线程要执行的程序内容
        for i in range(10):
            print("我是线程A")

class B(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        for i in range(10):
            print("我是线程B")

# 实例化线程A为t1
t1 = A()
# 启动线程t1
t1.start()
# 实例化线程B为t2
t2 = B()
# 启动线程t2,此时与t1同时执行
t2.start()

'''
output:

我是线程A
我是线程A
我是线程A
我是线程A
我是线程A
我是线程A
我是线程A
我是线程B
我是线程B
我是线程B
我是线程B
我是线程B
我是线程B
我是线程B
我是线程B
我是线程B
我是线程B
我是线程A
我是线程A
我是线程A

Process finished with exit code 0
'''

队列基础

import queue

q = queue.Queue()
q.put('A')
q.task_done()
q.put('B')
q.task_done()
q.put('C')
q.task_done()
print(q.get())  # A
print(q.get())  # B
print(q.get())  # C

实例代码,将上面的单线程微信爬虫改为多线程

import threading
import queue
import re
import urllib.request
import time
import urllib.error

urlqueue = queue.Queue()
# 模拟成浏览器
headers = ("User-Agent",
           "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.122 Safari/537.36 SE 2.X MetaSr 1.0")
opener = urllib.request.build_opener()
opener.addheaders = [headers]
# 将opener安装为全局
urllib.request.install_opener(opener)
listurl = []

# 使用代理服务器的函数
def use_proxy(proxy_addr, url):
    try:
        proxy = urllib.request.ProxyHandler({'http': proxy_addr})
        opener = urllib.request.build_opener(proxy, urllib.request.HTTPHandler)
        urllib.request.install_opener(opener)
        data = urllib.request.urlopen(url).read().decode('utf-8')
        return data
    except urllib.error.URLError as e:
        if hasattr(e, "code"):
            print(e.code)
        if hasattr(e, "reason"):
            print(e.reason)
        time.sleep(10)
    except Exception as e:
        print("exception:" + str(e))
        time.sleep(1)

# 线程1,专门获取对应网址并处理为真实网址
class geturl(threading.Thread):
    def __init__(self, key, pagestart, pageend, proxy, urlqueue):
        threading.Thread.__init__(self)
        self.pagestart = pagestart
        self.pageend = pageend
        self.proxy = proxy
        self.urlqueue = urlqueue
        self.key = key

    def run(self):
        page = self.pagestart
        # 编码关键词key
        keycode = urllib.request.quote(key)
        # 编码"&page"
        pagecode = urllib.request.quote("&page")
        for page in range(self.pagestart, self.pageend + 1):
            url = "http://weixin.sogou.com/weixin?type=2&query=" + keycode + pagecode + str(page)
            # 用代理服务器爬,解决IP被封杀问题
            data1 = use_proxy(self.proxy, url)
            # 列表页url正则
            listurlpat = '<div class="txt-box">.*?(http://.*?)"'
            listurl.append(re.compile(listurlpat, re.S).findall(data1))
        # 便于调试
        print("获取到" + str(len(listurl)) + "页")
        for i in range(0, len(listurl)):
            # 等一等线程2,合理分配资源
            time.sleep(7)
            for j in range(0, len(listurl[i])):
                try:
                    url = listurl[i][j]
                    # 处理成真实url,读者亦可以观察对应网址的关系自行分析,采集网址比真实网址多了一串amp
                    url = url.replace("amp;", "")
                    print("第" + str(i) + "i" + str(j) + "j次入队")
                    self.urlqueue.put(url)
                    self.urlqueue.task_done()
                except urllib.error.URLError as e:
                    if hasattr(e, "code"):
                        print(e.code)
                    if hasattr(e, "reason"):
                        print(e.reason)
                    time.sleep(10)
                except Exception as e:
                    print("exception:" + str(e))
                    time.sleep(1)

# 线程2,与线程1并行执行,从线程1提供的文章网址中依次爬取对应文章信息并处理
class getcontent(threading.Thread):
    def __init__(self, urlqueue, proxy):
        threading.Thread.__init__(self)
        self.urlqueue = urlqueue
        self.proxy = proxy

    def run(self):
        html1 = '''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
        <html xmlns="http://www.w3.org/1999/xhtml">
        <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>微信文章页面</title>
        </head>
        <body>'''
        fh = open("D:/Python35/myweb/part6/2.html", "wb")
        fh.write(html1.encode("utf-8"))
        fh.close()
        fh = open("D:/Python35/myweb/part6/2.html", "ab")
        i = 1
        while (True):
            try:
                url = self.urlqueue.get()
                data = use_proxy(self.proxy, url)
                titlepat = "<title>(.*?)</title>"
                contentpat = 'id="js_content">(.*?)id="js_sg_bar"'
                title = re.compile(titlepat).findall(data)
                content = re.compile(contentpat, re.S).findall(data)
                thistitle = "此次没有获取到"
                thiscontent = "此次没有获取到"
                if (title != []):
                    thistitle = title[0]
                if (content != []):
                    thiscontent = content[0]
                dataall = "<p>标题为:" + thistitle + "</p><p>内容为:" + thiscontent + "</p><br>"
                fh.write(dataall.encode("utf-8"))
                print("第" + str(i) + "个网页处理")  # 便于调试
                i += 1
            except urllib.error.URLError as e:
                if hasattr(e, "code"):
                    print(e.code)
                if hasattr(e, "reason"):
                    print(e.reason)
                time.sleep(10)
            except Exception as e:
                print("exception:" + str(e))
                time.sleep(1)
        fh.close()
        html2 = '''</body>
        </html>
        '''
        fh = open("D:/Python35/myweb/part6/2.html", "ab")
        fh.write(html2.encode("utf-8"))
        fh.close()

# 并行控制程序,若60秒未响应,并且存url的队列已空,则判断为执行成功
class conrl(threading.Thread):
    def __init__(self, urlqueue):
        threading.Thread.__init__(self)
        self.urlqueue = urlqueue

    def run(self):
        while (True):
            print("程序执行中")
            time.sleep(60)
            if (self.urlqueue.empty()):
                print("程序执行完毕!")
                exit()

key = "人工智能"
proxy = "119.6.136.122:80"
proxy2 = ""
pagestart = 1  # 起始页
pageend = 2  # 抓取到哪页
# 创建线程1对象,随后启动线程1
t1 = geturl(key, pagestart, pageend, proxy, urlqueue)
t1.start()
# 创建线程2对象,随后启动线程2
t2 = getcontent(urlqueue, proxy)
t2.start()
# 创建线程3对象,随后启动线程3
t3 = conrl(urlqueue)
t3.start()

爬虫浏览器的伪装技术

常见的反爬虫机制:

  1. 通过分析用户请求的Headers信息进行反爬虫。
  2. 通过检测用户行为进行反爬虫,比如判断同一个IP在短时间内是否频繁访问对应网站等进行分析。
  3. 通过动态页面增加爬虫爬取的难度,达到反爬虫的目的。

解决思路:

第一种,伪装成浏览器,设置好Headers字段,如User-Agent ,Referer

第二种,代理服务器,

第三种,利用一些工具软件,比如selenium+phanotomJS

访问优酷官网,用Fiddler查看头信息

GET / HTTP/1.1
Host: www.youku.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://home.firefoxchina.cn/
Cookie: __ysuid=1515480667604MVu; premium_cps=3746062939_40%7C73%7C658%7C238___
Connection: keep-alive
Upgrade-Insecure-Requests: 1

各字段含义如下:

GET / HTTP/1.1 请求方式与请求协议
Host  请求的服务器地址

其他常见字段:

常见字段1

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept 字段主要用来表示浏览器能够支持的内容类型有哪些
text/html 表示HTML文档
application/xhtml+xml 表示XHTML文档
application/xml 表示XML文档
q 代表权重系数,值介于0和1之间
所有这一行字段信息表示:浏览器可以支持text/html,application/xhtml+xml,application/xml,*/*内容类型,支持的优先顺序从左到右依次排列。

常见字段2

Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3

Accept-Language 主要表示浏览器所支持的语言类型
zh-CN 表示简体中文,zh代表中文,CN代表简体
en-US 代表英语(美国)语言
en 代表英语语言
所以这一行字段信息表示,浏览器支持zh-CN,zh,en-US,en等语言

常见字段3

Accept-Encoding: gzip, deflate

Accept-Language 字段主要用来表示浏览器支持的压缩编码格式有哪些
gzip 是压缩编码的一种
deflate 是一种无损数据压缩算法
这一行字段信息表示浏览器可以支持gzip,deflate压缩编码

常见字段4

Referer: http://home.firefoxchina.cn/

Referer 字段表示来源网站的地址,就是说从那个网站过来的

常见字段5

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0

User-Agent 表示用户代理
Mozilla/5.0 表示浏览器及版本信息
Windows NT 10.0; WOW64; rv:52.0表示客户端操作系统对应信息
Gecko/20100101  表示网页版引擎对应信息
Firefox/52.0 表示火狐浏览器

常见字段6

Connection: keep-alive

Connection 表示客户端与服务端链接类型,对应的字段值主要有两种:
keep-alive 表示持久性链接
close 表示单方面关闭链接,让链接断开
所以此时,这一字段信息表示客户端与服务端的链接是持久性链接