从0开始基于python3用scrapy爬取数据




 

 

摘要:

本文主要介绍0基础从python3的安装到使用scrapy框架抓取某母婴电商的数据并简单分析。主要包括以下内容: 

 


 

第一爬:官网

第一次爬取数据强烈推荐内网系统,免得一个代理问题就让激情退却了:

 

安装python3

下载地址:https://www.python.org/downloads/

查看安装版本以验证安装成功:

$:python -V

 

注意:

1)python3和python2的差别非常大,大家在google的时候要看清楚是针对python2还是python3的。

比如在python2中,print是这样的 

Python代码  收藏代码
  1. print "hello world"  

而在python3中,print是函数要加上括号,变成了:

Python代码  收藏代码
  1. print("hello world")  

2) python有很多多多多的库,可以通过pip install命令下载,例如下载我们前面画的那个柱状图的库:

Python代码  收藏代码
  1. pip install matplotlib  

   在python2.7和python3.6之后,pip已经集成到python安装包里面了,不用再单独下载。

3)如果是Mac用户,系统已经自带了python2.7,博主不敢随便升级,于是另外安装了python3。并不像网上很多老帖子写的需要装environment来切换两个python版本(猜测是针对windows用户的)。系统自带的python用python启动,而python3用命令python3启动,pip也是一样,有pip命令和pip3命令。就像是两个应用一样非常方便。

Shell代码  收藏代码
  1. Zhuos-MacBook-Pro:demo jo$ python -V  
  2. Python 2.7.13  
  3. Zhuos-MacBook-Pro:demo jo$ python3 -V  
  4. Python 3.6.1  

     Windows用户如果要装两个版本...装好了来跟博主share下经验教训。

4)本文之后所有的代码示例都是基于python3的。

 

编辑器安装:

推荐使用pycharm,地址: https://www.jetbrains.com/pycharm/download/

 

爬取官网

第一个爬虫代码,我们暂时不考虑url的相对路径问题,也不考虑公司代理问题等...下面的代码应该是最简单的了,再简单就是伪代码了。

 

Python代码  收藏代码
  1. import re  
  2. import urllib  
  3. import urllib.request  
  4. from collections import deque  
  5.   
  6. url = "http://www.you_compay_home_page.com"  
  7.   
  8. queue = deque()  
  9. visited = set()  
  10. total_count = 1  
  11. queue.append(url)  
  12.   
  13. while queue:  
  14.     url = queue.popleft()  
  15.     visited |= {url}  
  16.     print("正在抓取第 " + str(total_count) + " 个, " + url)  
  17.     total_count += 1  
  18.     urllop = urllib.request.urlopen(url, timeout=1)  
  19.     if "html" not in urllop.getheader('Content-Type'):  
  20.         print(urllop + " 不是html页面,忽略!")  
  21.         continue  
  22.     try:  
  23.         data = urllop.read().decode("utf-8")  
  24.     except Exception as e:  
  25.         print(e)  
  26.         continue  
  27.     count_per_page = 0  
  28.       
  29.     linkre = re.compile('href="(.+?)"')  
  30.     for x in linkre.findall(data):  
  31.         if 'https://www.you_compay_home_page.com/' in x and x not in visited:  
  32.             count_per_page += 1  
  33.             queue.append(x) #注意调试的时候注释本行,以免对服务器造成压力  
  34.             print("加入待爬页面:" + x)  
  35.     print("本页面共加入待爬页面:" + str(count_per_page))  

 解释:

line1~4

     导入我们需要的依赖库:re是正则表达式,顾名思义urllib与urllib.request是针对url,deque是针对队列的.

line6~11

     url:爬虫的入口;queue:存放要爬取的页面;visited:存放已经爬取过的页面,防止重复爬取。

   1)url,queue,visited,total_count都是对象引用。

   2)  python不用像java一样需要定义引用的类型,每个引用都知道自己指向的是什么类型的对象。

   3)  python中没有原子数据类型,都是对象,例如line17,total_count也是一个int型的对象。

line13:有没花括号了,取而代之的是冒号+缩进,例如if, while, try...

line18: urlopen这个方法只有一个url是必填参数,timeout是有默认值的参数,但是我们传入了自定义的值。

Python代码  收藏代码
  1. urllop = urllib.request.urlopen(url, timeout=1)   

 

看看这个方法的定义:

Python代码  收藏代码
  1. def urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,  
  2.             *, cafile=None, capath=None, cadefault=False, context=None):  
第一个url是必填参数,后面的data,timeout,cafile等是默认参数。我们调用该方法的时候,只提供了必选参数url和默认参数timeout。
python中参数有5可以任意组合,但是必须符合下面的顺序:
必选参数 → 默认参数 → 可变参数 → 命名关键字参数 → 关键字参数

必选参数:url

默认参数:data, timeout

可变参数:无。 例如C中的数组指针*pointer,指向一个数组或元祖(元祖,既不可变的数组)。

命名关键字参数:cafile,capath,cadefault,context。 *,标志其后的参数是命名关键字参数,既只能指定参数名为cafile,capath,cadefault,context的参数。

关键字参数:无。**pointer,类似于C中传入一个二维指针,指向一个dict,包含了一组key-value对。

关于python的参数,Read More:http://blog.csdn.net/downing114/article/details/70257602?locationNum=2&fps=1

line29~35:通过正则表达式找到该页面包含的其他链接,加入到queue中等待被访问。

     编写我们的pattern:href="(.+?)",用这个pattern匹配读取到的网页数据data,匹配上的数据按照括号分组,linkre.findall(data)将返回分组数据,这里即为该页面中的其他链接。

 

 第二爬:内网看板

看板系统需要登录,我们需要安装抓包的工具查看登录时发送给服务器的报文,让爬虫也能依样画葫芦的去登录。


 

 

抓包工具:Mac上我使用的是Charles试用版,Windwos上推荐Fiddler。

通过抓包工具我们可以看到发送到服务器的有4个field,用户名和密码都是自己录入的,另外两个字段应该是页面自己生成的,进一步查看页面,我们可以看到这两个字段在页面上的位置,通过正则表达式让爬虫看到token的值,以便后续和用户名,密码一起发送给服务器。

Tips: 对chrom和firefox,通过:

     Windows:control+shift+i

     Mac:command+option+i

打开开发者工具,切到Elements tab,就可以通过指到页面的元素,方便快捷的看到这个元素对应的html代码了。



 

 

Okay,我们想好了方法,就可以用手写伪装成浏览器获取token的代码了:

Java代码  收藏代码
  1. import re  
  2. import urllib  
  3. import urllib.request  
  4. import http.cookiejar  
  5. from collections import deque  
  6.   
  7. print("\n\n*************** Step 1: visit index page and get the token generated in server side ******************")  
  8. url = "http://gitlab.your_company_addr.com/users/sign_in"  
  9. header = {  
  10.     'Connection''Keep-Alive',  
  11.     'Accept''text/html, application/xhtml+xml, */*',  
  12.     'Accept-Language''en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3',  
  13.     'User-Agent''Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko',  
  14.     'Host''www.zhihu.com',  
  15.     'DNT''1'  
  16.     # 'Cookie''_gitlab_session=f00c50db7dc2c83989419079760e5786'  
  17. }  
  18.   
  19.   
  20. def getToken(data):  
  21.     cer = re.compile('name=\"authenticity_token\" value=\"(.+?)\"')  
  22.     strlist = cer.findall(data)  
  23.     return strlist[0]  
  24.   
  25.   
  26. def getOpener(head):  
  27.     # deal with the Cookies  
  28.     cj = cj = http.cookiejar.CookieJar()  
  29.     opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))  
  30.     header = []  
  31.     for key, value in head.items():  
  32.         elem = (key, value)  
  33.         header.append(elem)  
  34.     opener.addheaders = header  
  35.     return opener  
  36.   
  37.   
  38. opener = getOpener(header)  
  39. op = opener.open(url)  
  40. data = op.read().decode("utf-8")  
  41. token = getToken(data)  
  42. print(token)  

 line9~17: 设置header的内容。我们这里构造的header在python中是一种dict数据结构。dict类似于java的map。存储key-value对。

 line20~23: 编写获取页面token的方法,入参是网页内容,出参是通过正则表达式提取到的token。

 line26~35: 编写方法获取包含了header的opener,之后我们都通过opener伪装成浏览器来打开网页。

 line26~35: 调用方法获取opener,打开网页,读取到了数据后用utf-8编码,然后再调用getToken方法从编码后的数据中提取到token。

Tips: 使用什么字符集对获取到的网页数据编码呢? 直接查看网页的head即可。Scrapy框架获取到页面不需要指定字符集,因为它对http报文解析得到了编码字符集。

 

Html代码  收藏代码
  1. <meta charset="utf-8">  
 

 

获取到了token后,我们开始登陆系统:
Python代码  收藏代码
  1. print("\n\n*************** Step 2: login the server with username, password and token generated before **********")  
  2. url = "http://gitlab.your_company_addr.com/users/auth/ldapmain/callback"  
  3. postDict = {  
  4.     'utf8''✓',  
  5.     'username''zhuoyp001',  
  6.     'password''********',  
  7.     'authenticity_token': token  
  8. }  
  9.   
  10. postData = urllib.parse.urlencode(postDict)  
  11. postData = postData.encode('utf-8')  
  12. res = opener.open(url, postData)  
  13. print(res.status, res.reason)  
  14. if (res.status != 200):  
  15.     print("login failed")  
  16.     exit()  
  17.   
  18. print('login successfully!')  
 line3~line8:构造http post方法要post的数据。这也是一个dict数据结构,在提交给服务器前,需要进行url encode一下。
 
Python代码  收藏代码
  1. print("\n\n*************** Step 3: begin scraping... **********")  
  2. baseUrl = "http://gitlab.your_company_addr.com/explore/projects"  
  3. project_queue = deque()  
  4. visited = set()  
  5. current_page = 1  
  6. page_amount = 3  
  7. url = baseUrl  
  8. while current_page <= page_amount:  
  9.     url = baseUrl + "?page=" + str(current_page)  
  10.     print("正在抓取第 " + str(current_page) + " 页, " + url)  
  11.     try:  
  12.         urllop = opener.open(url, timeout=1000)  
  13.         data = urllop.read().decode("utf-8")  
  14.     except:  
  15.         print("error")  
  16.         continue  
  17.     linkre = re.compile('<a class="project" .*href="(/.+?)"')  # match projects  
  18.     for x in linkre.findall(data):  
  19.         project_queue.append(x)  
  20.         print("加入待爬页面:" + x)  
  21.     current_page += 1  
  22.     url = baseUrl + "?page=" + str(current_page)  

 line2: 第一个示例爬取主页中,我们从官网主页入手,找到了link就继续爬。而在本例中,我们已经知道了要爬的网站的结构,可以通过一个固定的baseUrl加上页码获得每个列表页面地址,再访问并解析每个列表页面,获得这个列表页面包含的project详细页面的地址,再将详细页面地址存在内存等待爬取。

 line3~7:跟第一个示例一样,project_queue是要爬的页面,visited是已经访问过的页面,current_page是当前页面,page_amount是通过正则表达式获得的总页面页数。(这里省略了正则取总页数的代码,直接设置为line13: 获取到一个列表页面

line17:在这个列表页面,凡是class定义为class的a标签,都是指向一个project,我们将这个project的地址放入project_queue中等待爬取。

 

通过解析完所有的列表页面,就将所有的project的地址放入了project_queue了,接下来就是逐个访问project_queue中的页面,将我们需要的信息提取出来:

Python代码  收藏代码
  1. print("\n\n*************** Step 4: visit each project and aggregate data")  
  2.   
  3.   
  4. class Issue:  
  5.     def __init__(self, project=None, open=0, closed=0):  
  6.         self.project = project  
  7.         self.open = open  
  8.         self.closed = closed  
  9.   
  10. projects_found = 0  
  11. project_list = []  
  12. while project_queue:  
  13.     project = project_queue.popleft()  
  14.     project_url = "http://gitlab.your_company_addr.com" + project + "/issues"  
  15.     try:  
  16.         project = str(project).rsplit(sep="/", maxsplit=1)[-1]  
  17.         issue = Issue(project)  
  18.         project_page = opener.open(project_url, timeout=1000)  
  19.         data = project_page.read().decode("utf-8")  
  20.   
  21.         openre = re.compile('<span>Open</span>.*<span class="badge">(.+?)</span>')  
  22.         for openNum in openre.findall(data):  
  23.             projects_found += 1  
  24.             issue.open = int(openNum)  
  25.         #省略对其他字段的提取  
  26.         project_list.append(issue)  
  27.     except:  
  28.         print("error page: " + project_url)  
  29.         traceback.print_exc()  
  30.         continue  

line4~8:定义了一个Issue类,包括project的名字,以及project open的issue个数,closed的issue个数.

     self:只要是类的成员方法,第一个参数都是self,调用这个方法的当前的对象,相当于java的this。

   __init__:python中的构造方法。

line12:python中空的字符串、空集合、0,在取他们的bool量的时候,默认都是false。

line13~14:从project_queue中获取到一个项目地址的相对路径,构造这个project的issue地址。

line18~24:从project issue页面获取到数据,通过解析得到我们想要的数据,封装到issue对象中。

line26:将每个issue加入到list中。最终我们要分析和制图的入参即是这个list。

 

最后我们得到一共多少个项目,其中每个项目的open的issue有多少个,closed的issue有多少个。那么我们可以进行图表展示了。先上一个画出来的示例图看看:



 

 

Python代码  收藏代码
  1. print("\n\n*************** Step 5: analyze data and write pic")  
  2. project_names = []  
  3. project_open = []  
  4. project_closed = []  
  5. N = 0  
  6. for i in range(len(project_list)):  
  7.     if project_list[i].all > 0:  
  8.         project_names.append(project_list[i].project)  
  9.         project_open.append(project_list[i].open)  
  10.         project_closed.append(project_list[i].closed)  
  11.         N += 1  
  12.   
  13. names = tuple(project_names)  
  14. open = tuple(project_open)  
  15. closed = tuple(project_closed)  
  16.   
  17. ind = np.arange(N)  # the x locations for the groups  
  18. width = 0.65  # the width of the bars: can also be len(x) sequence  
  19.   
  20. p1 = plt.bar(ind, open, width, color='#d62728')  
  21. p2 = plt.bar(ind, closed, width, bottom=open)  
  22.   
  23. plt.ylabel('issues')  
  24. plt.title('project issues')  
  25. plt.xticks(ind, names)  
  26. plt.yticks(np.arange(08110))  
  27. plt.legend((p1[0], p2[0]), ('open issues''closed issues'))  
  28.   
  29. plt.show()  

line20,21,25:这里都是调用pylab的函数,可以看到它画图需要的是三个list,因此在13~15行将我们的List<Issue>转为三个list。

对于pylab,不是我们爬虫讨论的重点,有兴趣自己google吧。

 

第三爬:Scrapy

 

安装scrapy

scrapy需要wheel来安装一些依赖,首先安装wheel:

Python代码  收藏代码
  1. pip install wheel  

验证:

Python代码  收藏代码
  1. wheel  

使用pip安装scrapy(博主没有成功,在windows下有问题。。大家可以试试,不行再按后面的操作安装):

Python代码  收藏代码
  1. pip install Scrapy  

Tip:如果是Mac用户,记得pip要用pip3哦~ 

 

如果上述有报错失败了,可以尝试使用wheel文件方式安装:

先卸载scrapy

Python代码  收藏代码
  1. pip uninstall Scrapy  

下载scrapy和它依赖的wheel文件:twisted, lxml,scrapy

下载地址:http://www.lfd.uci.edu/~gohlke/pythonlibs/

找到Twisted, lxml和Scrapy的whl文件.

    Twisted‑17.1.0‑cp36‑cp36m‑win32.whl

    lxml‑3.7.3‑cp36‑cp36m‑win32.whl

    Scrapy‑1.4.0‑py2.py3‑none‑any.whl

注意:cp36指的是python的版本,而win32不是我们windows的版本,如果win64不成功可以试试win32.

下载并安装,例如:

Python代码  收藏代码
  1. pip install  xxx.whl  

验证scrapy安装成功:

 

Python代码  收藏代码
  1. scrapy  

reference:  http://scrapy-chs.readthedocs.io/zh_CN/0.24/intro/tutorial.html

 

创建scrapy项目

Python代码  收藏代码
  1. scrapy startproject demo  

在spiders目录下新建一个spider类,取名为demo,

Python代码  收藏代码
  1. import scrapy  
  2. import logging  
  3.   
  4. header = {  
  5.     'User-Agent''Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',  
  6.     'Accept''text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',  
  7.     'Accept-Charset''ISO-8859-1,utf-8;q=0.7,*;q=0.3',  
  8.     # 'Accept-Encoding': LxxxSpider.py'gzip, deflate',  
  9.     'Accept-Language''en-US,en;q=0.8',  
  10.     'Host''www.lxxx.com.cn',  
  11.     'Connection''keep-alive'}  
  12.   
  13.   
  14. # scrapy crawl demo  
  15.   
  16. class Demopider(scrapy.Spider):  
  17.     name = "demo"  
  18.   
  19.     def start_requests(self):  
  20.         urls = [  
  21.             'http://www.lxxx.com.cn/',  
  22.         ]  
  23.         for url in urls:  
  24.             yield scrapy.Request(url=url, callback=self.parse_home, headers=header)  
  25.   
  26.     def parse_home(self, response):  
  27.         content = response.body.decode("gb2312""ignore")  
  28.         logging.debug("Open home page \n\n" + content)  

line4~11:设置我们的header伪装浏览器,应该不陌生了:)

line16:定义我们的DemoSpider类,它继承了scrapy.Spider类。

line17:给我们的spider类取名字,后面我们将用这个名字启动爬虫。

line19~24:start_requests这个方法类定义入爬虫的入口页面,以及访问了这个页面后回调的函数。

line24:python的yeild一般用于一个for循环中,作为一个generator,这里代码解析的时候并不会真的产生一个request,而是在for循环内执行到这一句的时候,才计算如何产生request。这里代表产生一个url的访问,数据取回来后用self.parse_home来解析取回页面。

详细可以百度之.这里简单refer一个:https://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/

line24~28:编写在24行调用的回调函数。

 

Scrappy Proxy设置

1)在demo/middlewares.py文件下新建ProxyMiddleware类

 

Python代码  收藏代码
  1. class ProxyMiddleware(object):  
  2.     # overwrite process request  
  3.     def process_request(self, request, spider):  
  4.         # Set the location of the proxy  
  5.         request.meta['proxy'] = "http://host:port"  

 

2)在demo/settings.py文件末增加对ProxyMiddleware的配置:

 

Python代码  收藏代码
  1. DOWNLOADER_MIDDLEWARES = {  
  2.     'scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware'110,  
  3.     'demo.middlewares.ProxyMiddleware'100,  
  4. }   

 

middleware是一个dict,key是middleware class path,value是 the middleware orders。Order越小越靠近engine,越大越靠近downloader。这是什么意思呢?请看下图:



此图是scrapy的架构图,spider可以看成我们写的代码,如何用正则解析确认哪些页面需要。我们定义好了后调用yield scrapy.Request就将这个请求送到了引擎中(图中步骤1)。引擎通过scheduler(步骤23), dowloader(步骤45)等获取到了response后,将页面再返回给spider,spider执行我们代码中的 scrapy.Request中的回调函数(步骤6),解析返回的reponse后在发起request(步骤7),在步骤4<-->5,6<-->7中,紫色的hook就是middlewares,这些middleware按顺序排列。

对于DOWNLOADER_MIDDLEWARES,数字越小的越靠近引擎,即越先执行process_request方法,数字越大的越靠近downloader,即越先执行process_response方法。

 

例如,如果优先执行的process_request返回了response,后面middleware的process_request或者process_exeception就不会被执行了;如果返回的none,则后面的process_request会继续执行直到有response返回;如果返回的是一个request(是的,你没看错),则这个response不会继续执行了,而是reschedule request。

The DOWNLOADER_MIDDLEWARES setting is merged with the DOWNLOADER_MIDDLEWARES_BASE setting defined in Scrapy (and not meant to be overridden) and then sorted by order to get the final sorted list of enabled middlewares: the first middleware is the one closer to the engine and the last is the one closer to the downloader.

 

Java代码  收藏代码
  1. class DownloaderMiddlewareManager(MiddlewareManager):  
  2.     def __init__(self, *middlewares):  
  3.         self.middlewares = middlewares  
  4.         self.methods = defaultdict(list)  
  5.         for mw in middlewares:  
  6.             self._add_middleware(mw)  
  7.   
  8.     def _add_middleware(self, mw):  
  9.         if hasattr(mw, 'process_request'):  
  10.             self.methods['process_request'].append(mw.process_request)  
  11.         if hasattr(mw, 'process_response'):  
  12.             self.methods['process_response'].insert(0, mw.process_response)  
  13.         if hasattr(mw, 'process_exception'):  
  14.             self.methods['process_exception'].insert(0, mw.process_exception)  

 这是scrapy的源码,对于每个middleware,如果它有process_reques方法,就把它加入到process_request middleware list里面;反之如果有process_response方法,就按加入到process_response middleware list中。

所以,在我们的例子中,在发起request时,是先执行order 顺序小的,即先设置我们自己定义的proxy。

refer to:https://stackoverflow.com/questions/6623470/scrapy-middleware-order

 

在下面的渲染ps部分,我们将实现自己的downloader,编码process_response方法,由于要等待页面渲染完成再下载,因此我们这个downloader的order将被设置成999。

还是不知道该设置成多少order对吧?这里有内置的middleware的order,可以和内置的比大小来设置自己的order。refer to:https://docs.scrapy.org/en/latest/topics/settings.html#std:setting-DOWNLOADER_MIDDLEWARES_BASE

 

 

Python代码  收藏代码
  1. {  
  2.     'scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware'100,  
  3.     'scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware'300,  
  4.     'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware'350,  
  5.     'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware'400,  
  6.     'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware'500,  
  7.     'scrapy.downloadermiddlewares.retry.RetryMiddleware'550,  
  8.     'scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware'560,  
  9.     'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware'580,  
  10.     'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware'590,  
  11.     'scrapy.downloadermiddlewares.redirect.RedirectMiddleware'600,  
  12.     'scrapy.downloadermiddlewares.cookies.CookiesMiddleware'700,  
  13.     'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware'750,  
  14.     'scrapy.downloadermiddlewares.stats.DownloaderStats'850,  
  15.     'scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware'900,  
  16. }  
 

 

启动demo爬虫

 

Python代码  收藏代码
  1. scrapy crawl demo  
debug demo

 在settings的同级目录下新建一个py文件,比如命名为run.py,用这个命令来执行我们上面的启动命令:

Python代码  收藏代码
  1. # -*- coding: utf-8 -*-  
  2. from scrapy import cmdline  
  3.   
  4. name = 'demo'  
  5. cmd = 'scrapy crawl {0}'.format(name)  
  6. cmdline.execute(cmd.split())  

 然后就可以打断点了,打完断点,在run.py文件上右键点“debug”,启动程序。就可以debug了

 

 

 

解析并下载网页

Python代码  收藏代码
  1. def start_requests(self):  
  2.     urls = [  
  3.         'http://www.lxxx.com.cn/',  
  4.     ]  
  5.     for url in urls:  
  6.         yield scrapy.Request(url=url, callback=self.parse_home, headers=header)  
  7.   
  8. def parse_home(self, response):  
  9.     content = response.body.decode("gb2312""ignore")  
  10.     linkre = re.compile(  
  11.             '<a .*href="((/|http://www.lxxx.com.cn/|http://s.lxxx.com.cn/|http://item.lxxx.com.cn/)product/.+?)"')  
  12.     for link in linkre.findall(content):  
  13.         url = str(link[0])  
  14.         full_url = response.urljoin(url)  
  15.         try:  
  16.             logging.debug("|--Will opne pages with its full url : " + full_url)  
  17.             yield scrapy.Request(full_url, callback=self.parse_page)  
  18.         except Exception as e:  
  19.             logging.debug("\n\nError: " + e)  
  20.             continue  
  21.   
  22. def parse_page(self, response):  
  23.     url = response.url  
  24.     product_detail = response.xpath('//div[@ng-app="singleApp"]')  
  25.     total_page = response.xpath('//li[@id="fenyes"]/span/text()')  
  26.     logging.debug("|-Opened one page : " + url)  
  27.     if product_detail != []:  
  28.         logging.debug("|--Found one detail page: " + url)  
  29.         DemoSpider.download_detail(response)  
  30.     elif product_detail == [] and total_page != []:  
  31.         logging.debug("|--Found one list page: " + url)  
  32.   
  33.         brand_lst = response.xpath('//span[@class="damon_brand"]/text()').extract()  
  34.         DemoSpider.add_brands(brand_lst)  
  35.   
  36.         logging.debug("|-In a list page : " + url)  
  37.         total_page = response.xpath('//li[@id="fenyes"]/span/text()').extract_first()  
  38.         total_page = total_page[total_page.index('共') + 1:total_page.index('页')]  
  39.         logging.debug("|-Analyze pagesize : " + url + " pagesize: " + total_page)  
  40.         query_str = DemoSpider.analyze_quer_str(url)  
  41.         for page in range(1, int(total_page)):  
  42.             next_list_page = "http://www.lxxx.com.cn/newweb/ajaxfile/skuslie.php?random=0.6151283582927061" + query_str + "&page=" + str(  
  43.                     page)  
  44.             logging.debug("|--Will go the next page : " + next_list_page + " page: " + str(page))  
  45.             yield scrapy.Request(url=next_list_page, callback=self.parse_list, headers=header)  
  46.     else:  
  47.         logging.debug("|--Found one unknown page : " + url)  

 line2~6:设置要爬的网站的入口地址,这里只有一个,lxxx的主页。取回来的入口页面都用self.parse_home解析。

line10:解析主页的函数,定义主页中包含的我们要继续爬取的url的正则表达式。

line12~20:遍历匹配的url地址,通过urljoin方法将路径都转换成绝对路径,然后继续产生访问这些url的request,并用parse_page函数来解析这些url。

line22~47:parse_page函数,解析当前页面,当前页面有三种可能:

    第一种是产品详细页面(line28~29),包括商品定价,描述等。这种页面是我们需要的目标页面,直接下载line29;

    第二种是商品的列表页面(line30~45),包含了分页,每一页都连接了很多商品详细页面。这类需要进一步解析总共的页面并进一步访问每页,对每个分页页面,执行parse_list (line45)进一步解析出其每页的产品。

    第三种是其他页面,比如报错页面等,暂时直接忽略掉。

 

JS渲染

大部分页面下载后都能正常显示,但是发现有几个页面如下,是使用的angularjs进行渲染页面的,我们下载页面的时候,页面的渲染还没有完成,因此我们需要的数据都还是angularjs的代码。通过selenium操作chromedriver来等待页面渲染完成后,我们在读取response的数据。如下图一是用了selenium渲染后与渲染前的对比:

 

为了等待js渲染完成再下载页面,我们需要编写downloader中间件,采用selenium来渲染js。

scrapy的中间件知识refer to:http://scrapy-chs.readthedocs.io/zh_CN/1.0/topics/downloader-middleware.html

 

创建一个渲染js的middleware:

 

Python代码  收藏代码
  1. class JavaScriptMiddleware(object):  
  2.     def process_request(self, request, spider):  
  3.         print("Chrome is starting...")  
  4.         driver = webdriver.Chrome()  # PhantomJS与angularjs有点问题,我们用chrome  
  5.         driver.get(request.url)  
  6.         time.sleep(3)  
  7.         body = driver.page_source  
  8.         print("访问" + request.url)  
  9.         return HtmlResponse(driver.current_url, body=body, encoding='utf8', request=request)  
 line4:需要用到chromedriver,通过以下命令安装:
Python代码  收藏代码
  1. brew install chromedriver --verbose   
 line6:这里我们等了3秒等待页面加载完成,这会大大的延迟爬取时间。优化方案可以是加个循环条件判断期望的一个值是否已经出现了,来判断是否加载完成。

 

在settings里面设置该JavaScriptMiddleware proxy:

Python代码  收藏代码
  1. DOWNLOADER_MIDDLEWARES = {  
  2.     'scrapy.contrib.downloadermiddleware.httpproxy.HttpProxyMiddleware'110,  
  3.     'demo.middlewares.ProxyMiddleware'100,  
  4.     'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware'None,  
  5.     'demo.middlewares.JavaScriptMiddleware'543  
  6. }  

 line4:对于scrapy内置的middlewares,默认都是启动的,如果不想用,要手动的设置他们的order为None。






展开阅读全文

没有更多推荐了,返回首页