python爬虫实践

模拟登陆与文件下载

爬取http://moodle.tipdm.com上面的视频并下载

模拟登陆

由于泰迪杯网站问题,测试之后发现无法用正常的账号密码登陆,这里会使用访客账号登陆。

我们先打开泰迪杯的登陆界面,打开开发者工具,选择Network选单,点击访客登陆。

注意到index.php的资源请求是一个POST请求,我们把视窗拉倒最下面,看到表单数据(Form data),浏览器在表单数据中发送了两个变量,分别是usernamepassword,两个变量的值都是guest。这就是我们需要告诉网站的信息了。

 

知道了这些信息,我们就可以使用requesst来模拟登陆了。

import requests

s = requests.Session()

data = {

    'username': 'guest',

    'password': 'guest',

}

r = s.post('http://moodle.tipdm.com/login/index.php', data)

print(r.url)

我们引入requests包。但我们这次并没有直接使用request.post(),而是在第二行先创建了一个Session实例sSession实例可以将浏览过程中的cookies保存下来。cookies指网站为了辨别用户身份而储存在用户本地终端上的数据(通常经过加密)泰迪网站要聪明一点,不是只用GET请求传递的数据来确认用户身份,而是要用保存在本地的cookies来确认用户身份(你再也不能伪装成隔壁老王了)。在requests中我们只要创建一个Session实例(比如这里的s),然后之后的请求都用Session实例s来发送,cookies的事情就不用管了。s.post()是用来发送POST请求的(requests.get()发送GET请求同理s.get()是用来发送get请求的。POST请求是一定要向服务器发送一个表单数据(form data)的。

我们看到泰迪网站要求上传的表单数据就是usernamepassword,两者的值都是guest,所以在python里面我们创建一个dict,命名为data,里面的数据就输入usernamepassword。最后再用s把数据post到网址,模拟登陆就完成了。

 

我们运行一下代码,

 

登陆成功了可以看到网址跳转到了泰迪教程的首页,和在浏览器里面的行为是一样的。这样我吗就实现了模拟登陆一个网站。

 

视频下载

我们进入到我们要下载的视频的页面http://moodle.tipdm.com/course/view.php?id=16),然后对要下载的链接进行审查元素。

 

元素都在`a`标签中,所有这样的a标签(a tag)都在<div class="activityinstance">标签中。所以我们只要找到所有的classacticityinstancediv标签,然后提取里面a标签的href属性,就知道视频的地址了同样的,我们使用beautiful soup包来实现我们想要的功能。

from bs4 import BeautifulSoup

r = s.get('http://moodle.tipdm.com/course/view.php?id=16')

soup = BeautifulSoup(r.text, 'lxml')

divs = soup.find_all("div", class_='activityinstance')

for div in divs:

    url = div.a.get('href')

    print(url)

 

现在所有的代码看起来应该是这样的:

import requests

from bs4 import BeautifulSoup

data = {

    'username': 'guest',

    'password': 'guest',

}

s = requests.Session()

r = s.post('http://moodle.tipdm.com/login/index.php', data)

r = s.get('http://moodle.tipdm.com/course/view.php?id=16')

soup = BeautifulSoup(r.text, 'lxml')

divs = soup.find_all("div", class_='activityinstance')

for div in divs:

    url = div.a.get('href')

    print(url)

运行一下,

你已经拿到了所有的网址我们点开其中的一个网址,看看里面的结构:

 

可以看到下载链接已经在你面前了,我们对它进行审查元素,看到了一个.mp4的下载地址,那下一步我们就是要获取这个mp4的下载地址。

for div in divs[1:]:    # 注意这里也出现了改动

    url = div.a.get('href')

    r = s.get(url)

    soup = BeautifulSoup(r.text, 'lxml')

    target_div = soup.find('div', class_='resourceworkaround')

    target_url = target_div.a.get('href')

    print(target_url)

divs[1:]的意思是我们忽视掉divs列表(list)中的第一个元素,然后进行下面的操作。

 

到目前为止,你的代码看起来应该是这样的:

import requests

from bs4 import BeautifulSoup

data = {

    'username': 'guest',

    'password': 'guest',

}

 

s = requests.Session()

r = s.post('http://moodle.tipdm.com/login/index.php', data)

r = s.get('http://moodle.tipdm.com/course/view.php?id=16')

soup = BeautifulSoup(r.text, 'lxml')

divs = soup.find_all("div", class_='activityinstance')

for div in divs[1:]:     # 注意这里也出现了改动

    url = div.a.get('href')

    r = s.get(url)

    soup = BeautifulSoup(r.text, 'lxml')

    target_div = soup.find('div', class_='resourceworkaround')

    target_url = target_div.a.get('href')

    print(target_url)

 

运行一下代码:

恭喜你,你成功获取到了视频的下载地址。现在将我在这里提供的代码复制到你的代码前面:

def download(url, s):

    import urllib, os

    file_name = urllib.parse.unquote(url)

    file_name = file_name[file_name.rfind('/') + 1:]

    try:

        r = s.get(url, stream=True, timeout = 2)

        chunk_size = 1000

        timer = 0

        length = int(r.headers['Content-Length'])

        print('downloading {}'.format(file_name))

        if os.path.isfile('./' + file_name):

                    print('  file already exist, skipped')

                    return

        with open('./' + file_name, 'wb') as f:

            for chunk in r.iter_content(chunk_size):

                timer += chunk_size

                percent = round(timer/length, 4) * 100

                print('\r {:4f}'.format((percent)), end = '')

                f.write(chunk)

        print('\r  finished    ')

    except requests.exceptions.ReadTimeout:

        print('read time out, this file failed to download')

        return

    except requests.exceptions.ConnectionError:

        print('ConnectionError, this file failed to download')

        return

然后在你循环的末尾加上download(target_url, s)

 

现在整个代码看起来是这样的:

import requests

from bs4 import BeautifulSoup

data = {

    'username': 'guest',

    'password': 'guest',

}

def download(url, s):

    import urllib, os

    file_name = urllib.parse.unquote(url)

    file_name = file_name[file_name.rfind('/') + 1:]

    try:

        r = s.get(url, stream=True, timeout = 2)

        chunk_size = 1000

        timer = 0

        length = int(r.headers['Content-Length'])

        print('downloading {}'.format(file_name))

        if os.path.isfile('./' + file_name):

                    print('  file already exist, skipped')

                    return

        with open('./' + file_name, 'wb') as f:

            for chunk in r.iter_content(chunk_size):

                timer += chunk_size

                percent = round(timer/length, 4) * 100

                print('\r {:4f}'.format((percent)), end = '')

                f.write(chunk)

        print('\r  finished    ')

    except requests.exceptions.ReadTimeout:

        print('read time out, this file failed to download')

        return

    except requests.exceptions.ConnectionError:

        print('ConnectionError, this file failed to download')

        return

 

s = requests.Session()

r = s.post('http://moodle.tipdm.com/login/index.php', data)

 

r = s.get('http://moodle.tipdm.com/course/view.php?id=16')

soup = BeautifulSoup(r.text, 'lxml')

divs = soup.find_all("div", class_='activityinstance')

for div in divs[1:]:

    url = div.a.get('href')

    r = s.get(url)

    soup = BeautifulSoup(r.text, 'lxml')

    target_div = soup.find('div', class_='resourceworkaround')

    target_url = target_div.a.get('href')

    download(target_url, s)

 

运行一下,视频已经开始下载了

 

这样你已经成功学会了如何模拟登陆一个网站,并且学会了如何从网站上面下载一个文件。

 

排行榜小说批量下载

我们要爬取的是小说,排行榜的地址:http://www.qu.la/paihangbang/。先观察下网页的结构

 

很容易就能发现,每一个分类都是包裹在:<div class="index_toplist mright mbottom">之中,这种条理清晰的网站,大大方便了爬虫的编写。在当前页面找到所有小说的连接,并保存在列表即可。这里有个问题,就算是不同类别的小说,也是会重复出现在排行榜的。这样无形之间就会浪费爬取时间解决方法就是url_list = list(set(url_list)) 这里调用了一个list的构造函数set:这样就能保证列表里没有重复的元素了。

 

代码实现

 

1.网页抓取头:

import requests

from bs4 import BeautifulSoup

 

def get_html(url):

    try:

        r = requests.get(url,timeout=30)

        r.raise_for_status

        r.encoding='utf-8'

        return r.text

    except:

        return 'error!'

 

2.获取排行榜小说及其链接:

爬取每一类型小说排行榜, 按顺序写入文件, 文件内容为小说名字+小说链接 将内容保存到列表并且返回一个装满url链接的列表

def get_content(url):

 

    url_list = []

    html = get_html(url)

    soup = BeautifulSoup(html,'lxml')

 

    # 由于小说排版的原因,历史类和完本类小说不在一个div

    category_list = soup.find_all('div',class_='index_toplist mright mbottom')

    history_list = soup.find_all('div',class_='index_toplist mbottom')

 

    for cate in category_list:

        name = cate.find('div',class_='toptab').span.text

        with open('novel_list.csv','a+') as f:

            f.write('\n小说种类:{} \n'.format(name))

 

        book_list = cate.find('div',class_='topbooks').find_all('li')

 

        # 循环遍历出每一个小说的的名字,以及链接

        for book in book_list:

            link = 'http://www.qu.la/' + book.a['href']

            title = book.a['title']

            url_list.append(link)

 

            # 这里使用a模式写入,防止清空文件

            with open('novel_list.csv','a') as f:

                f.write('小说名:{} \t 小说地址:{} \n'.format(title,link))

 

    for cate in history_list:

        name = cate.find('div',class_='toptab').span.text

        with open('novel_list.csv','a') as f:

            f.write('\n小说种类: {} \n'.format(name))

        book_list = cate.find('div',class_='topbooks').find_all('li')

        for book in book_list:

            link = 'http://www.qu.la/' + book.a['href']

            title = book.a['title']

            url_list.append(link)

 

            with open('novel_list.csv','a') as f:

                f.write('小说名:{} \t 小说地址:{} \n'.format(title,link))

return url_list

 

3.获取单本小说的所有章节链接:

获取该小说每个章节的url地址,并创建小说文件

# 获取单本小说的所有章节链接

def get_txt_url(url):

 

    url_list = []

    html = get_html(url)

    soup = BeautifulSoup(html,'lxml')

    list_a = soup.find_all('dd')

    txt_name = soup.find('dt').text

 

    with open('C:/Users/Administrator/Desktop/小说/{}.txt'.format(txt_name),'a+') as f:

        f.write('小说标题:{} \n'.format(txt_name))

 

    for url in list_a:

        url_list.append('http://www.qu.la/' + url.a['href'])

 

return url_list,txt_name

 

4.获取单页文章的内容并保存到本地

从网上爬下来的文件很多时候都是带着<br>之类的格式化标签,可以通过一个简单的方法把它过滤掉:
html = get_html(url).replace('<br/>', '\n')
这里单单过滤了一种标签,并将其替换成‘\n’用于文章的换行,

def get_one_txt(url,txt_name):

 

    html = get_html(url).replace('<br/>','\n')

    soup = BeautifulSoup(html,'lxml')

    try:

        txt = soup.find('div',id='content').text

        title = soup.find('h1').text

 

        with open('C:/Users/Administrator/Desktop/小说/{}.txt'.format(txt.name),'a') as f:

            f.write(title + '\n\n')

            f.write(txt)

            print('当前小说:{}当前章节{}已经下载完毕'.format(txt_name,title))

    except:

        print('ERROR!')

 

5.主函数

def get_all_txt(url_list):

    

    for url in url_list:

        # 遍历获取当前小说的所有章节的目录,并且生成小说头文件

        page_list,txt_name = get_txt_url(url)

 

def main():

    # 小说排行榜地址

    base_url = 'http://www.qu.la/paihangbang/'

    # 获取排行榜中所有小说的url链接

    url_list = get_content(base_url)

    # 除去重复的小说

    url_list = list(set(url_list))

    get_all_txt(url_list)

 

if __name__ == '__main__':

main()

 

6.输出结果

 

 

 

多线程爬虫

最近想要抓取拉勾网的数据,最开始是使用Scrapy的,但是遇到了下面两个问题:1前端页面是用JS模板引擎生成的2接口主要是用POST提交参数的

目前不会处理使用JS模板引擎生成的HTML页面,用POST的提交参数的话,接口统一,也没有必要使用Scrapy,所以就萌生了自己写一个简单的Python爬虫的想法。该爬虫框架主要就是处理网络请求,这个简单的爬虫使用多线程来处理网络请求,使用线程来处理URL队列中的url,然后将url返回的结果保存在另一个队列中,其它线程读取这个队列中的数据,然后写到文件中去。该爬虫主要用下面几个部分组成。

 

1 URL队列和结果队列

将将要爬取url放在一个队列中,这里使用标准库Queue。访问url后的结果保存在结果队列中

初始化一个URL队列

from Queue import Queue

urls_queue = Queue()

out_queue = Queue()

 

请求线程

使用多个线程,不停的取URL队列中的url,并进行处理:

import threading

class ThreadCrawl(threading.Thread):

    def __init__(self, queue, out_queue):

        threading.Thread.__init__(self)

        self.queue = queue

        self.out_queue = out_queue

 

    def run(self):

        while True:

            item = self.queue.get()

            self.queue.task_down()

如果队列为空,线程就会被阻塞,直到队列不为空。处理队列中的一条数据后,就需要通知队列已经处理完该条数据。

 

处理线程

处理结果队列中的数据,并保存到文件中。如果使用多个线程的话,必须要给文件加上锁。

lock = threading.Lock()

f = codecs.open('out.txt', 'w', 'utf8')

当线程需要写入文件的时候,可以这样处理:

with lock:

    f.write(something)

抓取结果:

源码

代码还不完善,将会持续修改中。

from Queue import Queue

import threading

import urllib2

import time

import json

import codecs

from bs4 import BeautifulSoup

 

urls_queue = Queue()

data_queue = Queue()

lock = threading.Lock()

f = codecs.open('out.txt', 'w', 'utf8')

 

class ThreadUrl(threading.Thread):

 

    def __init__(self, queue):

        threading.Thread.__init__(self)

        self.queue = queue

 

    def run(self):

        pass

 

class ThreadCrawl(threading.Thread):

 

    def __init__(self, url, queue, out_queue):

        threading.Thread.__init__(self)

        self.url = url

        self.queue = queue

        self.out_queue = out_queue

 

    def run(self):

        while True:

            item = self.queue.get()

            data = self._data_post(item)

            try:

                req = urllib2.Request(url=self.url, data=data)

                res = urllib2.urlopen(req)

            except urllib2.HTTPError, e:

                raise e.reason

            py_data = json.loads(res.read())

            res.close()

            item['first'] = 'false'

            item['pn'] = item['pn'] + 1

            success = py_data['success']

            if success:

                print 'Get success...'

            else:

                print 'Get fail....'

            print 'pn is : %s' % item['pn']

            result = py_data['content']['result']

            if len(result) != 0:

                self.queue.put(item)

            print 'now queue size is: %d' % self.queue.qsize()

            self.out_queue.put(py_data['content']['result'])

            self.queue.task_done()

 

    def _data_post(self, item):

        pn = item['pn']

        first = 'false'

        if pn == 1:

            first = 'true'

        return 'first=' + first + '&pn=' + str(pn) + '&kd=' + item['kd']

 

    def _item_queue(self):

        pass

 

class ThreadWrite(threading.Thread):

 

    def __init__(self, queue, lock, f):

        threading.Thread.__init__(self)

        self.queue = queue

        self.lock = lock

        self.f = f

 

    def run(self):

        while True:

            item = self.queue.get()

            self._parse_data(item)

            self.queue.task_done()

 

    def _parse_data(self, item):

        for i in item:

            l = self._item_to_str(i)

            with self.lock:

                print 'write %s' % l

                self.f.write(l)

 

    def _item_to_str(self, item):

        positionName = item['positionName']

        positionType = item['positionType']

        workYear = item['workYear']

        education = item['education']

        jobNature = item['jobNature']

        companyName = item['companyName']

        companyLogo = item['companyLogo']

        industryField = item['industryField']

        financeStage = item['financeStage']

        companyShortName = item['companyShortName']

        city = item['city']

        salary = item['salary']

        positionFirstType = item['positionFirstType']

        createTime = item['createTime']

        positionId = item['positionId']

        return positionName + ' ' + positionType + ' ' + workYear + ' ' + education + ' ' + \

            jobNature + ' ' + companyLogo + ' ' + industryField + ' ' + financeStage + ' ' + \

            companyShortName + ' ' + city + ' ' + salary + ' ' + positionFirstType + ' ' + \

            createTime + ' ' + str(positionId) + '\n'

 

def main():

    for i in range(4):

        t = ThreadCrawl(

            'http://www.lagou.com/jobs/positionAjax.json', urls_queue, data_queue)

        t.setDaemon(True)

        t.start()

    datas = [

        {'first': 'true', 'pn': 1, 'kd': 'Java'}

        #{'first': 'true', 'pn': 1, 'kd': 'Python'}

    ]

    for d in datas:

        urls_queue.put(d)

    for i in range(4):

        t = ThreadWrite(data_queue, lock, f)

        t.setDaemon(True)

        t.start()

 

    urls_queue.join()

    data_queue.join()

 

    with lock:

        f.close()

    print 'data_queue siez: %d' % data_queue.qsize()

main()

 

爬虫的基本流程

网络爬虫的基本工作流程如下:

首先选取一部分精心挑选的种子URL

将种子URL加入任务队列

从待抓取URL队列中取出待抓取的URL,解析DNS,并且得到主机的ip,并将URL对应的网页下载下来,存储进已下载网页库中。此外,将这些URL放进已抓取URL队列。

分析已抓取URL队列中的URL,分析其中的其他URL,并且将URL放入待抓取URL队列,从而进入下一个循环。

解析下载下来的网页,将需要的数据解析出来。

数据持久话,保存至数据库中。

爬虫的抓取策略

在爬虫系统中,待抓取URL队列是很重要的一部分。待抓取URL队列中的URL以什么样的顺序排列也是一个很重要的问题,因为这涉及到先抓取那个页面,后抓取哪个页面。而决定这些URL排列顺序的方法,叫做抓取策略。下面重点介绍几种常见的抓取策略:

 

深度优先策略(DFS) 深度优先策略是指爬虫从某个URL开始,一个链接一个链接的爬取下去,直到处理完了某个链接所在的所有线路,才切换到其它的线路。
此时抓取顺序为:A -> B -> C -> D -> E -> F -> G -> H -> I -> J

广度优先策略(BFS) 宽度优先遍历策略的基本思路是,将新下载网页中发现的链接直接插入待抓取URL队列的末尾。也就是指网络爬虫会先抓取起始网页中链接的所有网页,然后再选择其中的一个链接网页,继续抓取在此网页中链接的所有网页。此时抓取顺序为:A -> B -> E -> G -> H -> I -> C -> F -> J -> D

 

技术栈

requests 人性化的请求发送

Bloom Filter 布隆过滤器,用于判重

XPath 解析HTML内容

murmurhash

●Anti crawler strategy 反爬虫策略

●MySQL 用户数据存储

 

1 调研目标网站背景

1.1 检查robots.txt

http://example.webscraping.com/robots.txt

 

# section 1

User-agent: BadCrawler

Disallow: /

 

# section 2

User-agent: *

Crawl-delay: 5

Disallow: /trap

 

# section 3

Sitemap: http://example.webscraping.com/sitemap.xml

 

●section 1 :禁止用户代理为BadCrawler的爬虫爬取该网站,除非恶意爬虫。

●section 2 :两次下载请求时间间隔5秒的爬取延迟。/trap 用于封禁恶意爬虫,会封禁1分钟不止。

●section 3 :定义一个Sitemap文件,下节讲。

 

1.2 检查网站地图

所有网页链接: http://example.webscraping.com/sitemap.xml

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">

<url>

<loc>http://example.webscraping.com/view/Afghanistan-1</loc>

</url>

<url>

<loc>

http://example.webscraping.com/view/Aland-Islands-2

</loc>

</url>

...

<url>

<loc>http://example.webscraping.com/view/Zimbabwe-252</loc>

</url>

</urlset>

 

1.3 估算网站大小

高级搜索参数:http://www.google.com/advanced_search 
Google搜索:site:http://example.webscraping.com/ 202个网页 
Google搜索:site:http://example.webscraping.com/view 117个网页

 

1.4 识别网站所有技术

buildwith模块可以检查网站构建的技术类型。 
安装库:pip install buildwith

>>> import builtwith

>>> builtwith.parse('http://example.webscraping.com')

{u'javascript-frameworks': [u'jQuery', u'Modernizr', u'jQuery UI'],

 u'web-frameworks': [u'Web2py', u'Twitter Bootstrap'],

 u'programming-languages': [u'Python'],

 u'web-servers': [u'Nginx']}

>>>

 

示例网址使用了PythonWeb2py框架,还使用了JavaScript库,可能是嵌入在HTML中的。这种容易抓取。其他建构类型: 
- AngularJS:内容动态加载 
- ASP.NET:爬取网页要用到会话管理和表单提交。

 

 

基本实现

下面是一个伪代码

import Queue

 

initial_page = "https://www.zhihu.com/people/gaoming623"

 

url_queue = Queue.Queue()

seen = set()

 

seen.insert(initial_page)

url_queue.put(initial_page)

 

while(True): #一直进行

    if url_queue.size()>0:

        current_url = url_queue.get()                  #拿出队例中第一个的url

        store(current_url)                                 #把这个url代表的网页存储好

        for next_url in extract_urls(current_url):  #提取把这个url里链向的url

            if next_url not in seen:     

                seen.put(next_url)

                url_queue.put(next_url)

    else:

        break

如果你直接加工一下上面的代码直接运行的话,你需要很长的时间才能爬下整个知乎用户的信息,毕竟知乎有6000万月活跃用户。更别说Google这样的搜索引擎需要爬下全网的内容了。那么问题出现在哪里?

布隆过滤器

需要爬的网页实在太多太多了,而上面的代码太慢太慢了。设想全网有N个网站,那么分析一下判重的复杂度就是N*log(N),因为所有网页要遍历一次,而每次判重用set的话需要log(N)的复杂度。OK,我知道pythonset实现是hash——不过这样还是太慢了,至少内存使用效率不高。通常的判重做法是怎样呢?Bloom Filter. 简单讲它仍然是一种hash的方法,但是它的特点是,它可以使用固定的内存(不随url的数量而增长)以O(1)的效率判定url是否已经在set中。可惜天下没有白吃的午餐,它的唯一问题在于,如果这个url不在set中,BF可以100%确定这个url没有看过。但是如果这个urlset中,它会告诉你:这个url应该已经出现过,不过我有2%的不确定性。注意这里的不确定性在你分配的内存足够大的时候,可以变得很小很少。

 

# bloom_filter.py

 

BIT_SIZE = 5000000

 

class BloomFilter:

   

    def __init__(self):

        # Initialize bloom filter, set size and all bits to 0

        bit_array = bitarray(BIT_SIZE)

        bit_array.setall(0)

 

        self.bit_array = bit_array

       

    def add(self, url):

        # Add a url, and set points in bitarray to 1 (Points count is equal to hash funcs count.)

        # Here use 7 hash functions.

        point_list = self.get_postions(url)

 

        for b in point_list:

            self.bit_array[b] = 1

 

    def contains(self, url):

        # Check if a url is in a collection

        point_list = self.get_postions(url)

 

        result = True

        for b in point_list:

            result = result and self.bit_array[b]

   

        return result

 

    def get_postions(self, url):

        # Get points positions in bit vector.

        point1 = mmh3.hash(url, 41) % BIT_SIZE

        point2 = mmh3.hash(url, 42) % BIT_SIZE

        point3 = mmh3.hash(url, 43) % BIT_SIZE

        point4 = mmh3.hash(url, 44) % BIT_SIZE

        point5 = mmh3.hash(url, 45) % BIT_SIZE

        point6 = mmh3.hash(url, 46) % BIT_SIZE

        point7 = mmh3.hash(url, 47) % BIT_SIZE

 

        return [point1, point2, point3, point4, point5, point6, point7]

 

建表

用户有价值的信息包括用户名、简介、行业、院校、专业及在平台上活动的数据比如回答数、文章数、提问数、粉丝数等等。

用户信息存储的表结构如下:

CREATE DATABASE `zhihu_user` /*!40100 DEFAULT CHARACTER SET utf8 */;

 

 

-- User base information table

CREATE TABLE `t_user` (

  `uid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,

  `username` varchar(50) NOT NULL COMMENT '用户名',                     

  `brief_info` varchar(400)  COMMENT '个人简介',

  `industry` varchar(50) COMMENT '所处行业',            

  `education` varchar(50) COMMENT '毕业院校',            

  `major` varchar(50) COMMENT '主修专业',

  `answer_count` int(10) unsigned DEFAULT 0 COMMENT '回答数',

  `article_count` int(10) unsigned DEFAULT 0 COMMENT '文章数',

  `ask_question_count` int(10) unsigned DEFAULT 0 COMMENT '提问数',

  `collection_count` int(10) unsigned DEFAULT 0 COMMENT '收藏数',

  `follower_count` int(10) unsigned DEFAULT 0 COMMENT '被关注数',

  `followed_count` int(10) unsigned DEFAULT 0 COMMENT '关注数',

  `follow_live_count` int(10) unsigned DEFAULT 0 COMMENT '关注直播数',

  `follow_topic_count` int(10) unsigned DEFAULT 0 COMMENT '关注话题数',

  `follow_column_count` int(10) unsigned DEFAULT 0 COMMENT '关注专栏数',

  `follow_question_count` int(10) unsigned DEFAULT 0 COMMENT '关注问题数',

  `follow_collection_count` int(10) unsigned DEFAULT 0 COMMENT '关注收藏夹数',

  `gmt_create` datetime NOT NULL COMMENT '创建时间',  

  `gmt_modify` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最后一次编辑',            

  PRIMARY KEY (`uid`)

) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户基本信息表';

网页下载后通过XPath进行解析,提取用户各个维度的数据,最后保存到数据库中。

 

反爬虫策略应对-Headers

一般网站会从几个维度来反爬虫:用户请求的Headers,用户行为,网站和数据加载的方式。从用户请求的Headers反爬虫是最常见的策略,很多网站都会对HeadersUser-Agent进行检测,还有一部分网站会对Referer进行检测(一些资源网站的防盗链就是检测Referer)。

如果遇到了这类反爬虫机制,可以直接在爬虫中添加Headers,将浏览器的User-Agent复制到爬虫的Headers中;或者将Referer值修改为目标网站域名。对于检测Headers的反爬虫,在爬虫中修改或者添加Headers就能很好的绕过。

cookies = {

    "d_c0": "AECA7v-aPwqPTiIbemmIQ8abhJy7bdD2VgE=|1468847182",

    "login": "NzM5ZDc2M2JkYzYwNDZlOGJlYWQ1YmI4OTg5NDhmMTY=|1480901173|9c296f424b32f241d1471203244eaf30729420f0",

    "n_c": "1",

    "q_c1": "395b12e529e541cbb400e9718395e346|1479808003000|1468847182000",

    "l_cap_id": "NzI0MTQwZGY2NjQyNDQ1NThmYTY0MjJhYmU2NmExMGY=|1480901160|2e7a7faee3b3e8d0afb550e8e7b38d86c15a31bc",

    "d_c0": "AECA7v-aPwqPTiIbemmIQ8abhJy7bdD2VgE=|1468847182",

    "cap_id": "N2U1NmQwODQ1NjFiNGI2Yzg2YTE2NzJkOTU5N2E0NjI=|1480901160|fd59e2ed79faacc2be1010687d27dd559ec1552a"

}

 

headers = {

    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.3",

    "Referer": "https://www.zhihu.com/"

}

 

r = requests.get(url, cookies = cookies, headers = headers)

 

反爬虫策略应对-代理IP

还有一部分网站是通过检测用户行为,例如同一IP短时间内多次访问同一页面,或者同一账户短时间内多次进行相同操作。

大多数网站都是前一种情况,对于这种情况,使用IP代理就可以解决。这样的代理ip爬虫经常会用到,最好自己准备一个。有了大量代理ip后可以每请求几次更换一个ip,这在requests或者urllib2中很容易做到,这样就能很容易的绕过第一种反爬虫。目前知乎已经对爬虫做了限制,如果是单个IP的话,一段时间系统便会提示异常流量,无法继续爬取了。因此代理IP池非常关键。网上有个免费的代理IP API: http://api.xicidaili.com/free2016.txt

import requests

import random

 

class Proxy:

 

    def __init__(self):

        self.cache_ip_list = []

 

    # Get random ip from free proxy api url.

    def get_random_ip(self):

        if not len(self.cache_ip_list):

            api_url = 'http://api.xicidaili.com/free2016.txt'

            try:

                r = requests.get(api_url)

                ip_list = r.text.split('\r\n')

                self.cache_ip_list = ip_list

            except Exception as e:

                # Return null list when caught exception.

                # In this case, crawler will not use proxy ip.

                print e

                return {}

 

        proxy_ip = random.choice(self.cache_ip_list)

        proxies = {'http': 'http://' + proxy_ip}

        return proxies

后续

使用日志模块记录爬取日志和错误日志

分布式任务队列和分布式爬虫

爬虫源代码:zhihu-crawler 下载之后通过pip安装相关三方包后,运行$ python crawler.py即可

 

转载于:https://www.cnblogs.com/tester-l/p/8194690.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值