↓ Skip to main content
  1. Posts/

How to use Scrapy with Django Application(转自medium)

·2519 words·6 mins
Note: This article is available in Chinese only. 本文暂无英文版本。 View original

在 medium 上看到一篇很赞的文章……无奈关键部分一律无法加载出来,挂了梯子也不行,很心塞。刚刚突然发现加载出来了,以防之后再次无法访问,所以搬运过来。

There are couple of articles on how to integrate Scrapy into a Django Application (or vice versa?). But most of them don’t cover a full complete example that includes triggering spiders from Django views. Since this is a web application, that must be our main goal.

What do we need ?
#

Before we start, it is better to specify what we want and how we want it. Check this diagram:

Scrapy 与 Django 集成的请求-响应流程

It shows how our app should work

  • Client sends a request with a URL to crawl it. (1)
  • Django triggers scrapy to run a spider to crawl that URL. (2)
  • Django returns a response to tell Client that crawling just started. (3)
  • scrapy completes crawling and saves extracted data into database. (4)
  • django fetches that data from database and return it to Client. (5)

Looks great and simple so far.

A note on that 5th statement
#

Django fetches that data from database and return it to Client. (5)

Neither Django nor client don’t know when Scrapy completes crawling. There is a callback method named pipeline_closed, but it belongs to Scrapy project. We can’t return a response from Scrapy pipelines. We use that method only to save extracted data into database.

Well eventually, in somewhere, we have to tell the client :

Hey! Crawling completed and i am sending you crawled data here.

There are two possible ways of this (Please comment if you discover more):

We can either use web sockets to inform client when crawling completed.

Or,

We can start sending requests on every 2 seconds (more? or less ?) from client to check crawling status after we get the "crawling started" response.

Web Socket solution sounds more stable and robust. But it requires a second service running separately and means more configuration. I will skip this option for now. But i would choose web sockets for my production-level applications.

Let’s write some code
#

It’s time to do some real job. Let’s start by preparing our environment.

Installing Dependencies
#

Create a virtual environment and activate it:

1$ python3.5 -m venv venv
2$ source venv/bin/activate
3$ pip install django scrapy scrapyd python-scrapyd-api

Scrapyd is a daemon service for running Scrapy spiders. You can discover its details from here.

python-scrapyd-api is a wrapper allows us to talk scrapyd from our Python program.

Note: I am going to use Python 3.5 for this project

Creating Django Project
#

Create a django project with an app named main :

1$ django-admin startproject iCrawler
2$ cd iCrawler && python manage.py startapp main

We also need a model to save our scraped data. Let’s keep it simple:

 1import json
 2from django.db import models
 3from django.utils import timezone
 4
 5class ScrapyItem(models.Model):
 6    unique_id = models.CharField(max_length=100, null=True)
 7    data = models.TextField() # this stands for our crawled data
 8    date = models.DateTimeField(default=timezone.now)
 9
10    # This is for basic and custom serialisation to return it to client as a JSON.
11    @property
12    def to_dict(self):
13        data = {
14            'data': json.loads(self.data),
15            'date': self.date
16        }
17        return data
18
19    def __str__(self):
20        return self.unique_id

Add main app into INSTALLED_APPS in settings.py And as a final step, migrations:

1$ python manage.py makemigrations
2$ python manage.py migrate

Let’s add a view and url to our main app:

详细代码
 1from uuid import uuid4
 2from urllib.parse import urlparse
 3from django.core.validators import URLValidator
 4from django.core.exceptions import ValidationError
 5from django.views.decorators.http import require_POST, require_http_methods
 6from django.shortcuts import render
 7from django.http import JsonResponse
 8from django.views.decorators.csrf import csrf_exempt
 9from scrapyd_api import ScrapydAPI
10from main.utils import URLUtil
11from main.models import ScrapyItem
12
13# connect scrapyd service
14scrapyd = ScrapydAPI('http://localhost:6800')
15
16
17def is_valid_url(url):
18    validate = URLValidator()
19    try:
20        validate(url) # check if url format is valid
21    except ValidationError:
22        return False
23
24    return True
25
26
27@csrf_exempt
28@require_http_methods(['POST', 'GET']) # only get and post
29def crawl(request):
30    # Post requests are for new crawling tasks
31    if request.method == 'POST':
32
33        url = request.POST.get('url', None) # take url comes from client. (From an input may be?)
34
35        if not url:
36            return JsonResponse({'error': 'Missing  args'})
37
38        if not is_valid_url(url):
39            return JsonResponse({'error': 'URL is invalid'})
40
41        domain = urlparse(url).netloc # parse the url and extract the domain
42        unique_id = str(uuid4()) # create a unique ID.
43
44        # This is the custom settings for scrapy spider.
45        # We can send anything we want to use it inside spiders and pipelines.
46        # I mean, anything
47        settings = {
48            'unique_id': unique_id, # unique ID for each record for DB
49            'USER_AGENT': 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)'
50        }
51
52        # Here we schedule a new crawling task from scrapyd.
53        # Notice that settings is a special argument name.
54        # But we can pass other arguments, though.
55        # This returns a ID which belongs and will be belong to this task
56        # We are going to use that to check task's status.
57        task = scrapyd.schedule('default', 'icrawler',
58            settings=settings, url=url, domain=domain)
59
60        return JsonResponse({'task_id': task, 'unique_id': unique_id, 'status': 'started' })
61
62    # Get requests are for getting result of a specific crawling task
63    elif request.method == 'GET':
64        # We were passed these from past request above. Remember ?
65        # They were trying to survive in client side.
66        # Now they are here again, thankfully. <3
67        # We passed them back to here to check the status of crawling
68        # And if crawling is completed, we respond back with a crawled data.
69        task_id = request.GET.get('task_id', None)
70        unique_id = request.GET.get('unique_id', None)
71
72        if not task_id or not unique_id:
73            return JsonResponse({'error': 'Missing args'})
74
75        # Here we check status of crawling that just started a few seconds ago.
76        # If it is finished, we can query from database and get results
77        # If it is not finished we can return active status
78        # Possible results are -> pending, running, finished
79        status = scrapyd.job_status('default', task_id)
80        if status == 'finished':
81            try:
82                # this is the unique_id that we created even before crawling started.
83                item = ScrapyItem.objects.get(unique_id=unique_id)
84                return JsonResponse({'data': item.to_dict['data']})
85            except Exception as e:
86                return JsonResponse({'error': str(e)})
87        else:
88            return JsonResponse({'status': status})

I tried to document the code as much as i can.

But the main trick is, unique_id. Normally, we save an object to database, then we get its ID. In our case, we are specifying its unique_id before creating it. Once crawling completed and client asks for the crawled data; we can create a query with that unique_id and fetch results.

And an url for this view:

 1from django.conf import settings
 2from django.conf.urls import url,static
 3from django.views.generic import TemplateView
 4from main import views
 5
 6urlpatterns = [
 7    url(r'^$', TemplateView.as_view(template_name='index.html'), name='home'),
 8    url(r'^api/crawl/', views.crawl, name='crawl'),
 9]
10
11# This is required for static files while in development mode. (DEBUG=TRUE)
12# No, not relevant to scrapy or crawling :)
13if settings.DEBUG:
14    urlpatterns += static.static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
15    urlpatterns += static.static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Creating Scrapy Project
#

It is better if we create Scrapy project under (or next to) our Django project. This makes easier to connect them together. So let’s create it under Django project folder:

1$ cd iCrawler
2$ scrapy startproject scrapy_app

Now we need to create our first spider from inside scrapy_app folder:

1$ cd scrapy_app
2$ scrapy genspider -t crawl icrawler https://google.com

i name spider as icrawler. You can name it as anything. Look -t crawl part. We specify a base template for our spider. You can see all available templates with:

1$ scrapy genspider -l
2Available templates:
3basic
4crawl
5csvfeed
6xmlfeed

Now we should have a folder structure like this:

Django 项目与 Scrapy 项目并列的目录结构

Connecting Scrapy to Django
#

In order to have access Django models from Scrapy, we need to connect them together. Go to settings.py file under scrapy_app/scrapy_app/ and put:

 1import os
 2import sys
 3
 4# DJANGO INTEGRATION
 5
 6sys.path.append(os.path.dirname(os.path.abspath('.')))
 7# Do not forget the change iCrawler part based on your project name
 8os.environ['DJANGO_SETTINGS_MODULE'] = 'iCrawler.settings'
 9
10# This is required only if Django Version > 1.8
11import django
12django.setup()
13
14# DJANGO INTEGRATION
15
16## Rest of settings are below ..

That’s it. Now let’s start scrapyd to make sure everything installed and configured properly. Inside scrapy_app/ folder run:

1$ scrapyd

This will start scrapyd and generate some outputs. Scrapyd also has a very minimal and simple web console. We don’t need it on production but we can use it to watch active jobs while developing. Once you start the scrapyd go to http://127.0.0.1:6800 and see if it is working.

Configuring Our Scrapy Project
#

Since this post is not about fundamentals of scrapy, i will skip the part about modifying spiders. You can create your spider with official documentation. I will put my example spider here, though:

 1class IcrawlerSpider(CrawlSpider):
 2    name = 'icrawler'
 3
 4    def __init__(self, *args, **kwargs):
 5        # We are going to pass these args from our django view.
 6        # To make everything dynamic, we need to override them inside __init__ method
 7        self.url = kwargs.get('url')
 8        self.domain = kwargs.get('domain')
 9        self.start_urls = [self.url]
10        self.allowed_domains = [self.domain]
11
12        IcrawlerSpider.rules = [
13           Rule(LinkExtractor(unique=True), callback='parse_item'),
14        ]
15        super(IcrawlerSpider, self).__init__(*args, **kwargs)
16
17    def parse_item(self, response):
18        # You can tweak each crawled page here
19        # Don't forget to return an object.
20        i = {}
21        i['url'] = response.url
22        return i

Above is icrawler.py file from scrapy_app/scrapy_app/spiders. Attention to __init__ method. It is important. If we want to make a method or property dynamic, we need to define it under __init__ method, so we can pass arguments from Django and use them here.

We also need to create a Item Pipeline for our scrapy project. Pipeline is a class for making actions over scraped items. From documentation:

Typical uses of item pipelines are:

  • cleansing HTML data
  • validating scraped data (checking that the items contain certain fields)
  • checking for duplicates (and dropping them)
  • storing the scraped item in a database

Yay! Storing the scraped item in a database. Now let’s create one. Actually there is already a file named pipelines.py inside scrapy_project folder. And also that file contains an empty-but-ready pipeline. We just need to modify it a little bit:

 1from main.models import ScrapyItem
 2import json
 3
 4class ScrapyAppPipeline(object):
 5    def __init__(self, unique_id, *args, **kwargs):
 6        self.unique_id = unique_id
 7        self.items = []
 8
 9    @classmethod
10    def from_crawler(cls, crawler):
11        return cls(
12            unique_id=crawler.settings.get('unique_id'), # this will be passed from django view
13        )
14
15    def close_spider(self, spider):
16        # And here we are saving our crawled data with django models.
17        item = ScrapyItem()
18        item.unique_id = self.unique_id
19        item.data = json.dumps(self.items)
20        item.save()
21
22    def process_item(self, item, spider):
23        self.items.append(item['url'])
24        return item

And as a final step, we need to enable (uncomment) this pipeline in scrapy settings.py file:

1# Configure item pipelines
2# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
3ITEM_PIPELINES = {
4    'scrapy_app.pipelines.ScrapyAppPipeline': 300,
5}

Don’t forget to restart scrapyd if it is working.

This scrapy project basically,

  • Crawls a website (comes from Django view)
  • Extract all URLs from website
  • Put them into a list
  • Save the list to database over Django models.

And that’s all for the back-end part. Django and Scrapy are both integrated and should be working fine.

Notes on Front-End Part
#

Well, this part is so subjective. We have tons of options. Personally I have built my front-end with React. The only part that is not subjective is usage of setInterval . Yes, let’s remember our options: web sockets and to send requests to server every X seconds.

To clarify base logic, this is simplified version of my React Component:

详细代码
 1class Home extends React.Component {
 2  constructor(props) {
 3      super(props)
 4      this.state = {
 5          url: '',
 6          crawlingStatus: null,
 7          data: null,
 8          taskID: null,
 9          uniqueID: null
10      }
11      this.statusInterval = 1
12  }
13
14  handleStartButton = (event) => {
15    if (!this.state.url) return false;
16
17    // send a post request to client when form button clicked
18    // django response back with task_id and unique_id.
19    // We have created them in views.py file, remember?
20    $.post('/api/crawl/', { url: this.state.url }, resp => {
21        if (resp.error) {
22            alert(resp.error)
23            return
24        }
25        // Update the state with new task and unique id
26        this.setState({
27            taskID: resp.task_id,
28            uniqueID: resp.unique_id,
29            crawlingStatus: resp.status
30        }, () => {
31            // ####################### HERE ########################
32            // After updating state,
33            // i start to execute checkCrawlStatus method for every 2 seconds
34            // Check method's body for more details
35            // ####################### HERE ########################
36            this.statusInterval = setInterval(this.checkCrawlStatus, 2000)
37        });
38    });
39  }
40
41  componentWillUnmount() {
42      // i create this.statusInterval inside constructor method
43      // So clear it anyway on page reloads or
44      clearInterval(this.statusInterval)
45  }
46
47  checkCrawlStatus = () => {
48      // this method do only one thing.
49      // Making a request to server to ask status of crawling job
50      $.get('/api/crawl/',
51            { task_id: this.state.taskID, unique_id: this.state.uniqueID }, resp => {
52          if (resp.data) {
53              // If response contains a data array
54              // That means crawling completed and we have results here
55              // No need to make more requests.
56              // Just clear interval
57              clearInterval(this.statusInterval)
58              this.setState({
59                  data: resp.data
60              })
61          } else if (resp.error) {
62              // If there is an error
63              // also no need to keep requesting
64              // just show it to user
65              // and clear interval
66              clearInterval(this.statusInterval)
67              alert(resp.error)
68          } else if (resp.status) {
69              // but response contains a `status` key and no data or error
70              // that means crawling process is still active and running (or pending)
71              // don't clear the interval.
72              this.setState({
73                  crawlingStatus: resp.status
74              });
75          }
76      })
77  }
78
79  render () {
80    // render componenet
81    return (<div></div>)
82  }
83}

You can discover the details by comments i added. It is quite simple actually.

Oh, that’s it. It took longer than i expected. Please leave a comment for any kind of feedback.

Related

爬虫学习笔记

·1182 words·3 mins
再次迫于生计。。。 参考了面向新人的 Python 爬虫学习资料 大致的学习路线为: 一: 简单的定向脚本爬虫( request — bs4 — re ) 二: 大型框架式爬虫( Scrapy 框架为主) 三:浏览器模拟爬虫 ( Mechanize 模拟 和 Selenium 模拟) 有 Python 基础和一点 html 基础的话,貌似上手是 0 难度的。

react 中setState的更新策略

·138 words·1 min
起因是想更新一个array类型的state,结果setState更新之后用console.log() debug 结果,发现结果特别玄学。。。 查了下发现this.setState是个异步操作。。。

react学习笔记

·681 words·2 mins
首先介绍一个fb家的快速开发react的工具 create-react-app 这个东西依赖node6.0或者更高版本。 关于在ubuntu 14.04上安装node ,可以参考这个链接 发现执行nvm install 6.0会没有任何响应…但是实际上已经安装好了。

lua学习笔记

·2216 words·5 mins
lua 是一门轻量级的脚本语言,好像比较适合写游戏?我在 太阳神三国杀 中见过很多 lua 脚本。由于 splash 的渲染脚本需要用 lua 来写,因此来学习一波。 直接上语法,看到了 python 和 pascal 的影子 orz。 详细代码 1-- Two dashes start a one-line comment. 2 3--[[ 4 Adding two ['s and ]'s makes it a 5 multi-line comment. 6--]] 7 8---------------------------------------------------- 9-- 1. Variables and flow control. 10---------------------------------------------------- 11 12num = 42 -- All numbers are doubles. 13-- Don't freak out, 64-bit doubles have 52 bits for 14-- storing exact int values; machine precision is 15-- not a problem for ints that need < 52 bits. 16 17s = 'walternate' -- Immutable strings like Python. 18t = "double-quotes are also fine" 19u = [[ Double brackets 20 start and end 21 multi-line strings.]] 22t = nil -- Undefines t; Lua has garbage collection. 23 24-- Blocks are denoted with keywords like do/end: 25while num < 50 do 26 num = num + 1 -- No ++ or += type operators. 27end 28 29-- If clauses: 30if num > 40 then 31 print('over 40') 32elseif s ~= 'walternate' then -- ~= is not equals. 33 -- Equality check is == like Python; ok for strs. 34 io.write('not over 40\n') -- Defaults to stdout. 35else 36 -- Variables are global by default. 37 thisIsGlobal = 5 -- Camel case is common. 38 39 -- How to make a variable local: 40 local line = io.read() -- Reads next stdin line. 41 42 -- String concatenation uses the .. operator: 43 print('Winter is coming, ' .. line) 44end 45 46-- Undefined variables return nil. 47-- This is not an error: 48foo = anUnknownVariable -- Now foo = nil. 49 50aBoolValue = false 51 52-- Only nil and false are falsy; 0 and '' are true! 53if not aBoolValue then print('twas false') end 54 55-- 'or' and 'and' are short-circuited. 56-- This is similar to the a?b:c operator in C/js: 57ans = aBoolValue and 'yes' or 'no' --> 'no' 58 59karlSum = 0 60for i = 1, 100 do -- The range includes both ends. 61 karlSum = karlSum + i 62end 63 64-- Use "100, 1, -1" as the range to count down: 65fredSum = 0 66for j = 100, 1, -1 do fredSum = fredSum + j end 67 68-- In general, the range is begin, end[, step]. 69 70-- Another loop construct: 71repeat 72 print('the way of the future') 73 num = num - 1 74until num == 0 75 76 77---------------------------------------------------- 78-- 2. Functions. 79---------------------------------------------------- 80 81function fib(n) 82 if n < 2 then return 1 end 83 return fib(n - 2) + fib(n - 1) 84end 85 86-- Closures and anonymous functions are ok: 87function adder(x) 88 -- The returned function is created when adder is 89 -- called, and remembers the value of x: 90 return function (y) return x + y end 91end 92a1 = adder(9) 93a2 = adder(36) 94print(a1(16)) --> 25 95print(a2(64)) --> 100 96 97-- Returns, func calls, and assignments all work 98-- with lists that may be mismatched in length. 99-- Unmatched receivers are nil; 100-- unmatched senders are discarded. 101 102x, y, z = 1, 2, 3, 4 103-- Now x = 1, y = 2, z = 3, and 4 is thrown away. 104 105function bar(a, b, c) 106 print(a, b, c) 107 return 4, 8, 15, 16, 23, 42 108end 109 110x, y = bar('zaphod') --> prints "zaphod nil nil" 111-- Now x = 4, y = 8, values 15..42 are discarded. 112 113-- Functions are first-class, may be local/global. 114-- These are the same: 115function f(x) return x * x end 116f = function (x) return x * x end 117 118-- And so are these: 119local function g(x) return math.sin(x) end 120local g; g = function (x) return math.sin(x) end 121-- the 'local g' decl makes g-self-references ok. 122 123-- Trig funcs work in radians, by the way. 124 125-- Calls with one string param don't need parens: 126print 'hello' -- Works fine. 127 128 129---------------------------------------------------- 130-- 3. Tables. 131---------------------------------------------------- 132 133-- Tables = Lua's only compound data structure; 134-- they are associative arrays. 135-- Similar to php arrays or js objects, they are 136-- hash-lookup dicts that can also be used as lists. 137 138-- Using tables as dictionaries / maps: 139 140-- Dict literals have string keys by default: 141t = {key1 = 'value1', key2 = false} 142 143-- String keys can use js-like dot notation: 144print(t.key1) -- Prints 'value1'. 145t.newKey = {} -- Adds a new key/value pair. 146t.key2 = nil -- Removes key2 from the table. 147 148-- Literal notation for any (non-nil) value as key: 149u = {['@!#'] = 'qbert', [{}] = 1729, [6.28] = 'tau'} 150print(u[6.28]) -- prints "tau" 151 152-- Key matching is basically by value for numbers 153-- and strings, but by identity for tables. 154a = u['@!#'] -- Now a = 'qbert'. 155b = u[{}] -- We might expect 1729, but it's nil: 156-- b = nil since the lookup fails. It fails 157-- because the key we used is not the same object 158-- as the one used to store the original value. So 159-- strings & numbers are more portable keys. 160 161-- A one-table-param function call needs no parens: 162function h(x) print(x.key1) end 163h{key1 = 'Sonmi~451'} -- Prints 'Sonmi~451'. 164 165for key, val in pairs(u) do -- Table iteration. 166 print(key, val) 167end 168 169-- _G is a special table of all globals. 170print(_G['_G'] == _G) -- Prints 'true'. 171 172-- Using tables as lists / arrays: 173 174-- List literals implicitly set up int keys: 175v = {'value1', 'value2', 1.21, 'gigawatts'} 176for i = 1, #v do -- #v is the size of v for lists. 177 print(v[i]) -- Indices start at 1 !! SO CRAZY! 178end 179-- A 'list' is not a real type. v is just a table 180-- with consecutive integer keys, treated as a list. 181 182---------------------------------------------------- 183-- 3.1 Metatables and metamethods. 184---------------------------------------------------- 185 186-- A table can have a metatable that gives the table 187-- operator-overloadish behavior. Later we'll see 188-- how metatables support js-prototypey behavior. 189 190f1 = {a = 1, b = 2} -- Represents the fraction a/b. 191f2 = {a = 2, b = 3} 192 193-- This would fail: 194-- s = f1 + f2 195 196metafraction = {} 197function metafraction.__add(f1, f2) 198 sum = {} 199 sum.b = f1.b * f2.b 200 sum.a = f1.a * f2.b + f2.a * f1.b 201 return sum 202end 203 204setmetatable(f1, metafraction) 205setmetatable(f2, metafraction) 206 207s = f1 + f2 -- call __add(f1, f2) on f1's metatable 208 209-- f1, f2 have no key for their metatable, unlike 210-- prototypes in js, so you must retrieve it as in 211-- getmetatable(f1). The metatable is a normal table 212-- with keys that Lua knows about, like __add. 213 214-- But the next line fails since s has no metatable: 215-- t = s + s 216-- Class-like patterns given below would fix this. 217 218-- An __index on a metatable overloads dot lookups: 219defaultFavs = {animal = 'gru', food = 'donuts'} 220myFavs = {food = 'pizza'} 221setmetatable(myFavs, {__index = defaultFavs}) 222eatenBy = myFavs.animal -- works! thanks, metatable 223 224-- Direct table lookups that fail will retry using 225-- the metatable's __index value, and this recurses. 226 227-- An __index value can also be a function(tbl, key) 228-- for more customized lookups. 229 230-- Values of __index,add, .. are called metamethods. 231-- Full list. Here a is a table with the metamethod. 232 233-- __add(a, b) for a + b 234-- __sub(a, b) for a - b 235-- __mul(a, b) for a * b 236-- __div(a, b) for a / b 237-- __mod(a, b) for a % b 238-- __pow(a, b) for a ^ b 239-- __unm(a) for -a 240-- __concat(a, b) for a .. b 241-- __len(a) for #a 242-- __eq(a, b) for a == b 243-- __lt(a, b) for a < b 244-- __le(a, b) for a <= b 245-- __index(a, b) <fn or a table> for a.b 246-- __newindex(a, b, c) for a.b = c 247-- __call(a, ...) for a(...) 248 249---------------------------------------------------- 250-- 3.2 Class-like tables and inheritance. 251---------------------------------------------------- 252 253-- Classes aren't built in; there are different ways 254-- to make them using tables and metatables. 255 256-- Explanation for this example is below it. 257 258Dog = {} -- 1. 259 260function Dog:new() -- 2. 261 newObj = {sound = 'woof'} -- 3. 262 self.__index = self -- 4. 263 return setmetatable(newObj, self) -- 5. 264end 265 266function Dog:makeSound() -- 6. 267 print('I say ' .. self.sound) 268end 269 270mrDog = Dog:new() -- 7. 271mrDog:makeSound() -- 'I say woof' -- 8. 272 273-- 1. Dog acts like a class; it's really a table. 274-- 2. function tablename:fn(...) is the same as 275-- function tablename.fn(self, ...) 276-- The : just adds a first arg called self. 277-- Read 7 & 8 below for how self gets its value. 278-- 3. newObj will be an instance of class Dog. 279-- 4. self = the class being instantiated. Often 280-- self = Dog, but inheritance can change it. 281-- newObj gets self's functions when we set both 282-- newObj's metatable and self's __index to self. 283-- 5. Reminder: setmetatable returns its first arg. 284-- 6. The : works as in 2, but this time we expect 285-- self to be an instance instead of a class. 286-- 7. Same as Dog.new(Dog), so self = Dog in new(). 287-- 8. Same as mrDog.makeSound(mrDog); self = mrDog. 288 289---------------------------------------------------- 290 291-- Inheritance example: 292 293LoudDog = Dog:new() -- 1. 294 295function LoudDog:makeSound() 296 s = self.sound .. ' ' -- 2. 297 print(s .. s .. s) 298end 299 300seymour = LoudDog:new() -- 3. 301seymour:makeSound() -- 'woof woof woof' -- 4. 302 303-- 1. LoudDog gets Dog's methods and variables. 304-- 2. self has a 'sound' key from new(), see 3. 305-- 3. Same as LoudDog.new(LoudDog), and converted to 306-- Dog.new(LoudDog) as LoudDog has no 'new' key, 307-- but does have __index = Dog on its metatable. 308-- Result: seymour's metatable is LoudDog, and 309-- LoudDog.__index = LoudDog. So seymour.key will 310-- = seymour.key, LoudDog.key, Dog.key, whichever 311-- table is the first with the given key. 312-- 4. The 'makeSound' key is found in LoudDog; this 313-- is the same as LoudDog.makeSound(seymour). 314 315-- If needed, a subclass's new() is like the base's: 316function LoudDog:new() 317 newObj = {} 318 -- set up newObj 319 self.__index = self 320 return setmetatable(newObj, self) 321end 322 323---------------------------------------------------- 324-- 4. Modules. 325---------------------------------------------------- 326 327 328--[[ I'm commenting out this section so the rest of 329-- this script remains runnable. 330-- Suppose the file mod.lua looks like this: 331local M = {} 332 333local function sayMyName() 334 print('Hrunkner') 335end 336 337function M.sayHello() 338 print('Why hello there') 339 sayMyName() 340end 341 342return M 343 344-- Another file can use mod.lua's functionality: 345local mod = require('mod') -- Run the file mod.lua. 346 347-- require is the standard way to include modules. 348-- require acts like: (if not cached; see below) 349local mod = (function () 350 <contents of mod.lua> 351end)() 352-- It's like mod.lua is a function body, so that 353-- locals inside mod.lua are invisible outside it. 354 355-- This works because mod here = M in mod.lua: 356mod.sayHello() -- Says hello to Hrunkner. 357 358-- This is wrong; sayMyName only exists in mod.lua: 359mod.sayMyName() -- error 360 361-- require's return values are cached so a file is 362-- run at most once, even when require'd many times. 363 364-- Suppose mod2.lua contains "print('Hi!')". 365local a = require('mod2') -- Prints Hi! 366local b = require('mod2') -- Doesn't print; a=b. 367 368-- dofile is like require without caching: 369dofile('mod2.lua') --> Hi! 370dofile('mod2.lua') --> Hi! (runs it again) 371 372-- loadfile loads a lua file but doesn't run it yet. 373f = loadfile('mod2.lua') -- Call f() to run it. 374 375-- loadstring is loadfile for strings. 376g = loadstring('print(343)') -- Returns a function. 377g() -- Prints out 343; nothing printed before now. 378 379--]] 380 381---------------------------------------------------- 382-- 5. References. 383---------------------------------------------------- 384 385--[[ 386 387I was excited to learn Lua so I could make games 388with the Löve 2D game engine. That's the why. 389 390I started with BlackBulletIV's Lua for programmers. 391Next I read the official Programming in Lua book. 392That's the how. 393 394It might be helpful to check out the Lua short 395reference on lua-users.org. 396 397The main topics not covered are standard libraries: 398 * string library 399 * table library 400 * math library 401 * io library 402 * os library 403 404By the way, this entire file is valid Lua; save it 405as learn.lua and run it with "lua learn.lua" ! 406 407This was first written for tylerneylon.com. It's 408also available as a github gist. Tutorials for other 409languages, in the same style as this one, are here: 410 411http://learnxinyminutes.com/ 412 413Have fun with Lua! 414 415--]]

golang 学习笔记

·358 words·1 min
先放资料,可能比较侧重于go在系统调用方面的内容. 这里不会记录详细的go的语法,只会记录学习的过程,踩到的坑,以及其他我认为值得记录的内容. go的switch语句终于是人类思维的语句了…匹配中了不需要加break..